gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated (61563d1b4 -> fa11663ce)


From: gnunet
Subject: [taler-wallet-core] branch master updated (61563d1b4 -> fa11663ce)
Date: Mon, 06 Nov 2023 15:55:24 +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 61563d1b4 aml exchange API
     new 3d1ab082d share header and footer in web utils
     new 4ee903eb5 aml ui
     new fa11663ce refactor bank to use components from web utils

The 3 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/aml-backoffice-ui/src/App.tsx             |  15 +-
 packages/aml-backoffice-ui/src/Dashboard.tsx       | 564 +++++----------------
 packages/aml-backoffice-ui/src/assets/home.svg     |   3 +
 packages/aml-backoffice-ui/src/assets/people.svg   |   3 +
 packages/aml-backoffice-ui/src/hooks/useCases.ts   |  11 +-
 .../src/hooks/useSettings.ts}                      |  41 +-
 packages/aml-backoffice-ui/src/pages.ts            |  52 +-
 packages/aml-backoffice-ui/src/pages/Cases.tsx     |  12 +-
 .../aml-backoffice-ui/src/pages/CreateAccount.tsx  |  36 +-
 .../src/pages/HandleAccountNotReady.tsx            |   3 +
 packages/aml-backoffice-ui/src/pages/Home.tsx      |   5 -
 packages/aml-backoffice-ui/src/pages/Officer.tsx   |  10 +-
 packages/aml-backoffice-ui/src/pages/Settings.tsx  |   5 -
 .../aml-backoffice-ui/src/pages/UnlockAccount.tsx  |  19 +-
 packages/aml-backoffice-ui/src/pages/Welcome.tsx   |   9 -
 packages/aml-backoffice-ui/src/route.ts            |  17 +-
 packages/aml-backoffice-ui/src/settings.ts         |   2 +-
 packages/aml-backoffice-ui/src/utils/Loading.tsx   |  43 --
 packages/aml-backoffice-ui/tailwind.config.js      |   8 +-
 packages/demobank-ui/src/pages/BankFrame.tsx       | 304 ++---------
 packages/demobank-ui/src/pages/LoginForm.tsx       |   6 +-
 .../demobank-ui/src/pages/OperationState/views.tsx |   6 +-
 .../src/pages/PaytoWireTransferForm.tsx            |   4 +-
 packages/demobank-ui/src/pages/QrCodeSection.tsx   |   4 +-
 .../demobank-ui/src/pages/RegistrationPage.tsx     |   4 +-
 .../demobank-ui/src/pages/ShowAccountDetails.tsx   |   4 +-
 .../src/pages/UpdateAccountPassword.tsx            |   4 +-
 .../demobank-ui/src/pages/WalletWithdrawForm.tsx   |   4 +-
 .../src/pages/WithdrawalConfirmationQuestion.tsx   |   4 +-
 .../src/pages/admin/CreateNewAccount.tsx           |   4 +-
 .../demobank-ui/src/pages/admin/RemoveAccount.tsx  |   4 +-
 .../src/pages/business/CreateCashout.tsx           |   4 +-
 .../src/pages/business/ShowCashoutDetails.tsx      |   4 +-
 .../src/assets/logo-2021.svg                       |   0
 .../src/assets/logo-white.svg                      |   0
 packages/web-util/src/components/Footer.tsx        |  44 ++
 .../src/components/GlobalNotificationBanner.tsx    |  28 +
 packages/web-util/src/components/Header.tsx        | 143 ++++++
 packages/web-util/src/components/Loading.tsx       |   4 +-
 ...otification.tsx => LocalNotificationBanner.tsx} |   2 +-
 packages/web-util/src/components/index.ts          |   5 +-
 41 files changed, 545 insertions(+), 899 deletions(-)
 create mode 100644 packages/aml-backoffice-ui/src/assets/home.svg
 create mode 100644 packages/aml-backoffice-ui/src/assets/people.svg
 copy packages/{demobank-ui/src/hooks/settings.ts => 
aml-backoffice-ui/src/hooks/useSettings.ts} (52%)
 delete mode 100644 packages/aml-backoffice-ui/src/pages/Home.tsx
 delete mode 100644 packages/aml-backoffice-ui/src/pages/Settings.tsx
 delete mode 100644 packages/aml-backoffice-ui/src/pages/Welcome.tsx
 delete mode 100644 packages/aml-backoffice-ui/src/utils/Loading.tsx
 copy packages/{aml-backoffice-ui => web-util}/src/assets/logo-2021.svg (100%)
 copy packages/{demobank-ui => web-util}/src/assets/logo-white.svg (100%)
 create mode 100644 packages/web-util/src/components/Footer.tsx
 create mode 100644 
packages/web-util/src/components/GlobalNotificationBanner.tsx
 create mode 100644 packages/web-util/src/components/Header.tsx
 rename packages/web-util/src/components/{ShowLocalNotification.tsx => 
LocalNotificationBanner.tsx} (93%)

diff --git a/packages/aml-backoffice-ui/src/App.tsx 
b/packages/aml-backoffice-ui/src/App.tsx
index 0e29279ff..52c86c273 100644
--- a/packages/aml-backoffice-ui/src/App.tsx
+++ b/packages/aml-backoffice-ui/src/App.tsx
@@ -1,9 +1,14 @@
 import { TranslationProvider } from "@gnu-taler/web-util/browser";
 import { h, VNode } from "preact";
-import { ExchangeAmlFrame, Main } from "./Dashboard.js";
+import { ExchangeAmlFrame } from "./Dashboard.js";
 import "./scss/main.css";
 import { ExchangeApiProvider } from "./context/config.js";
 import { getInitialBackendBaseURL } from "./hooks/useBackend.js";
+import { Router } from "./route.js";
+import { Pages } from "./pages.js";
+
+const pageList = Object.values(Pages);
+
 
 export function App(): VNode {
   const baseUrl = getInitialBackendBaseURL();
@@ -11,7 +16,13 @@ export function App(): VNode {
     <TranslationProvider source={{}}>
       <ExchangeApiProvider baseUrl={baseUrl} frameOnError={ExchangeAmlFrame}>
         <ExchangeAmlFrame>
-          <Main />
+          <Router
+            pageList={pageList}
+            onNotFound={() => {
+              window.location.href = Pages.cases.url
+              return <div>not found</div>;
+            }}
+          />
         </ExchangeAmlFrame>
       </ExchangeApiProvider>
     </TranslationProvider>
diff --git a/packages/aml-backoffice-ui/src/Dashboard.tsx 
b/packages/aml-backoffice-ui/src/Dashboard.tsx
index bd8a48c45..5d86836d4 100644
--- a/packages/aml-backoffice-ui/src/Dashboard.tsx
+++ b/packages/aml-backoffice-ui/src/Dashboard.tsx
@@ -1,13 +1,17 @@
-import { useNotifications } from "@gnu-taler/web-util/browser";
+import { Footer, GlobalNotificationsBanner, Header, LangSelector, notifyError, 
notifyException, useNotifications, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { Dialog, Transition } from "@headlessui/react";
 import { UserIcon, XCircleIcon } from "@heroicons/react/20/solid";
 import { CheckCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
 import { InformationCircleIcon } from "@heroicons/react/24/solid";
 import { ComponentChildren, Fragment, VNode, h } from "preact";
-import { useState } from "preact/hooks";
+import { useEffect, useErrorBoundary, useState } from "preact/hooks";
 import logo from "./assets/logo-2021.svg";
 import { Pages } from "./pages.js";
-import { Router, useCurrentLocation } from "./route.js";
+import { PageEntry, Router, useCurrentLocation } from "./route.js";
+import { uiSettings } from "./settings.js";
+import { TranslatedString } from "@gnu-taler/taler-util";
+import { useOfficer } from "./hooks/useOfficer.js";
+import { getAllBooleanSettings, getLabelForSetting, useSettings } from 
"./hooks/useSettings.js";
 
 function classNames(...classes: string[]) {
   return classes.filter(Boolean).join(" ");
@@ -78,6 +82,7 @@ const versionText = VERSION
  * writing text with the correct format
  */
 
+const pageList = Object.values(Pages);
 function LeftMenu() {
   const currentLocation = useCurrentLocation(pageList);
 
@@ -88,9 +93,9 @@ function LeftMenu() {
           <ul role="list" class="-mx-2 space-y-1">
             <li>
               <a
-                href={Pages.info.url}
+                href={Pages.cases.url}
                 class={classNames(
-                  Pages.info.url === currentLocation?.path
+                  Pages.cases.url === currentLocation?.path
                     ? "bg-indigo-700 text-white"
                     : "text-indigo-200 hover:text-white hover:bg-indigo-700",
                   "group flex gap-x-3 rounded-md p-2 text-sm leading-6 
font-semibold",
@@ -98,7 +103,7 @@ function LeftMenu() {
               >
                 <InformationCircleIcon
                   class={classNames(
-                    Pages.info.url === currentLocation?.path
+                    Pages.cases.url === currentLocation?.path
                       ? "text-white"
                       : "text-indigo-200 group-hover:text-white",
                     "h-6 w-6 shrink-0",
@@ -132,472 +137,143 @@ function LeftMenu() {
             </li>
           </ul>
         </li>
-        {/* <li>
-          <div class="text-xs font-semibold leading-6 text-indigo-200">
-            Info
-          </div>
-          <ul role="list" class="-mx-2 mt-2 space-y-1">
-            <li>
-              <a
-                href={Pages.info.url}
-                class={classNames(
-                  Pages.info.url === currentLocation?.path
-                    ? "bg-indigo-700 text-white"
-                    : "text-indigo-200 hover:text-white hover:bg-indigo-700",
-                  "group flex gap-x-3 rounded-md p-2 text-sm leading-6 
font-semibold",
-                )}
-              >
-                <span class="flex h-6 w-6 shrink-0 items-center justify-center 
rounded-lg border border-indigo-400 bg-indigo-500 text-[0.625rem] font-medium 
text-white">
-                  asd
-                </span>
-                <span class="truncate">qwe</span>
-              </a>
-            </li>
-          </ul>
-        </li> */}
-        {/* <li class="mt-auto">
-          <a
-            href={Pages.settings.url}
-            class={classNames(
-              Pages.settings.url === currentLocation?.path
-                ? "bg-indigo-700 text-white"
-                : "text-indigo-200 hover:text-white hover:bg-indigo-700",
-              "group -mx-2 flex gap-x-3 rounded-md p-2 text-sm leading-6 
font-semibold",
-            )}
-          >
-            <Cog6ToothIcon
-              class={classNames(
-                Pages.officer.url === currentLocation?.path
-                  ? "text-white"
-                  : "text-indigo-200 group-hover:text-white",
-                "h-6 w-6 shrink-0",
-              )}
-              aria-hidden="true"
-            />
-            Settings
-          </a>
-        </li> */}
       </ul>
     </nav>
   );
 }
 
+
 export function ExchangeAmlFrame({
   children,
 }: {
   children?: ComponentChildren;
 }): VNode {
-  const [sidebarOpen, setSidebarOpen] = useState(false);
+  const { i18n } = useTranslationContext();
 
-  return (
-    <Fragment>
-      <NavigationBar isOpen={sidebarOpen} setOpen={setSidebarOpen}>
-        <div class="flex grow flex-col gap-y-5 overflow-y-auto bg-indigo-600 
px-6 pb-4">
-          <div class="flex h-16 shrink-0 items-center">
-            <header class="flex items-center justify-between border-b 
border-white/5 ">
-              <h1 class="text-base font-semibold leading-7 text-white">
-                Exchange AML Backoffice
-              </h1>
-            </header>
-          </div>
-          <LeftMenu />
-          <Footer />
-        </div>
-      </NavigationBar>
-      <div class="lg:pl-72">
-        <TopBar
-          onOpenSidebar={() => {
-            setSidebarOpen(true);
-          }}
-        />
-        <Notifications />
-        {children}
-      </div>
-    </Fragment>
-  );
-}
+  const [error, resetError] = useErrorBoundary();
+
+  useEffect(() => {
+    if (error) {
+      if (error instanceof Error) {
+        notifyException(i18n.str`Internal error, please report.`, error)
+      } else {
+        notifyError(i18n.str`Internal error, please report.`, String(error) as 
TranslatedString)
+      }
+      resetError()
+    }
+  }, [error])
 
-export function Main(): VNode {
-  return <main class="py-10 px-4 sm:px-6 lg:px-8">
-    <div class="mx-auto max-w-3xl">
-      <Router
-        pageList={pageList}
-        onNotFound={() => {
-          return <div>not found</div>;
+  const officer = useOfficer();
+  const [settings, updateSettings] = useSettings();
+
+  return (<div class="min-h-full flex flex-col m-0 bg-slate-200" 
style="min-height: 100vh;">
+    <div class="bg-indigo-600 pb-32">
+      <Header
+        title="Exchange"
+        iconLinkURL={uiSettings.backendBaseURL ?? "#"}
+        onLogout={officer.state !== "ready" ? undefined : () => {
+          officer.lock()
         }}
-      />
+        sites={[]}
+        supportedLangs={["en", "es", "de"]}
+      >
+        <li>
+          <div class="text-xs font-semibold leading-6 text-gray-400">
+            <i18n.Translate>Preferences</i18n.Translate>
+          </div>
+          <ul role="list" class="space-y-1">
+            {getAllBooleanSettings().map(set => {
+              const isOn: boolean = !!settings[set]
+              return <li class="mt-2 pl-2">
+                <div class="flex items-center justify-between">
+                  <span class="flex flex-grow flex-col">
+                    <span class="text-sm text-black font-medium leading-6 " 
id="availability-label">
+                      {getLabelForSetting(set, i18n)}
+                    </span>
+                  </span>
+                  <button type="button" data-enabled={isOn} 
class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 
w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent 
transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 
focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-description"
+
+                    onClick={() => { updateSettings(set, !isOn); }}>
+                    <span aria-hidden="true" data-enabled={isOn} 
class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none 
inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition 
duration-200 ease-in-out"></span>
+                  </button>
+                </div>
+              </li>
+            })}
+          </ul>
+        </li>
+      </Header>
     </div>
-  </main>
-}
 
-const pageList = Object.values(Pages);
+    <GlobalNotificationsBanner />
 
-function NavigationBar({
-  isOpen,
-  setOpen,
-  children,
-}: {
-  isOpen: boolean;
-  setOpen: (v: boolean) => void;
-  children: ComponentChildren;
-}) {
-  return (
-    <Fragment>
-      <Transition.Root show={isOpen} as={Fragment}>
-        <Dialog
-          as="div"
-          /* @ts-ignore */
-          class="relative z-50 lg:hidden"
-          onClose={setOpen}
-        >
-          <Transition.Child
-            as={Fragment}
-            enter="transition-opacity ease-linear duration-300"
-            enterFrom="opacity-0"
-            enterTo="opacity-100"
-            leave="transition-opacity ease-linear duration-300"
-            leaveFrom="opacity-100"
-            leaveTo="opacity-0"
-          >
-            <div class="fixed inset-0 bg-gray-900/80" />
-          </Transition.Child>
+    <main class="-mt-32 flex grow ">
+      {officer.state !== "ready" ? undefined :
+        <Navigation />
+      }
+      <div class="flex mx-auto my-4">
+        <div class="rounded-lg bg-white px-5 py-6 shadow sm:px-6">
+          {children}
+        </div>
+      </div>
 
-          <div class="fixed inset-0 flex">
-            <Transition.Child
-              as={Fragment}
-              enter="transition ease-in-out duration-300 transform"
-              enterFrom="-translate-x-full"
-              enterTo="translate-x-0"
-              leave="transition ease-in-out duration-300 transform"
-              leaveFrom="translate-x-0"
-              leaveTo="-translate-x-full"
-            >
-              <Dialog.Panel class="relative mr-16 flex w-full max-w-xs flex-1">
-                <Transition.Child
-                  as={Fragment}
-                  enter="ease-in-out duration-300"
-                  enterFrom="opacity-0"
-                  enterTo="opacity-100"
-                  leave="ease-in-out duration-300"
-                  leaveFrom="opacity-100"
-                  leaveTo="opacity-0"
-                >
-                  <div class="absolute left-full top-0 flex w-16 
justify-center pt-5">
-                    <button
-                      type="button"
-                      class="-m-2.5 p-2.5"
-                      onClick={() => setOpen(false)}
-                    >
-                      <span class="sr-only">Close sidebar</span>
-                      <XMarkIcon
-                        class="h-6 w-6 text-white"
-                        aria-hidden="true"
-                      />
-                    </button>
-                  </div>
-                </Transition.Child>
-                {children}
-              </Dialog.Panel>
-            </Transition.Child>
-          </div>
-        </Dialog>
-      </Transition.Root>
+    </main>
 
-      <div class="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-72 
lg:flex-col">
-        {children}
-      </div>
-    </Fragment>
+    <Footer
+      testingUrl={localStorage.getItem("exchange-base-url") ?? undefined}
+      GIT_HASH={GIT_HASH}
+      VERSION={VERSION}
+    />
+  </div>
   );
 }
 
-function TopBar({ onOpenSidebar }: { onOpenSidebar: () => void }) {
+function Navigation(): VNode {
+  const { i18n } = useTranslationContext()
+  const pageList: Array<PageEntry> = [
+    Pages.officer,
+    Pages.cases
+  ]
   return (
-    <div class="relative flex h-16 justify-between">
-      <div class="relative z-10 flex p-2 lg:hidden">
-        <button
-          type="button"
-          onClick={() => {
-            onOpenSidebar();
-          }}
-          class="inline-flex items-center justify-center rounded-md p-2 
text-gray-400 hover:bg-gray-700 hover:text-gray-900 focus:outline-none 
focus:ring-2 focus:ring-inset focus:ring-gray-900"
-          aria-controls="mobile-menu"
-          aria-expanded="false"
-        >
-          <span class="sr-only">Open menu</span>
-          <svg
-            class="block h-6 w-6"
-            fill="none"
-            viewBox="0 0 24 24"
-            stroke-width="1.5"
-            stroke="currentColor"
-            aria-hidden="true"
-          >
-            <path
-              stroke-linecap="round"
-              stroke-linejoin="round"
-              d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
-            />
-          </svg>
-          <svg
-            class="hidden h-6 w-6"
-            fill="none"
-            viewBox="0 0 24 24"
-            stroke-width="1.5"
-            stroke="currentColor"
-            aria-hidden="true"
-          >
-            <path
-              stroke-linecap="round"
-              stroke-linejoin="round"
-              d="M6 18L18 6M6 6l12 12"
-            />
-          </svg>
-        </button>
-      </div>
-      <div class="relative z-0 flex flex-1 items-center justify-center px-2 
sm:absolute sm:inset-0">
-        <div class="w-full sm:max-w-xs flex flex-1 items-center 
justify-center">
-          <img
-            class="h-8 w-auto"
-            src={logo}
-            alt="Taler"
-            style={{ height: 35, margin: 10 }}
-          />
-        </div>
-      </div>
-      {/* <div class="relative z-10 flex items-center lg:hidden">dd</div> */}
-    </div>
-  );
-}
+    <div class="flex gap-y-5 w-48 bg-indigo-600 divide-y rounded-r-lg 
divide-cyan-800 overflow-y-auto overflow-x-clip">
 
-//   return (
-//     <div class="sticky top-0 z-40 flex h-16 shrink-0 items-center gap-x-4 
border-b border-gray-200 bg-white px-4 shadow-sm sm:gap-x-6 sm:px-6 lg:px-8">
-//       <button
-//         type="button"
-//         class="-m-2.5 p-2.5 text-gray-700 lg:hidden"
-//         onClick={onOpenSidebar}
-//       >
-//         <span class="sr-only">Open sidebar</span>
-//         <Bars3Icon class="h-6 w-6" aria-hidden="true" />
-//       </button>
+      <nav class="flex flex-1 flex-col mx-4 mt-4 mb-2">
+        <ul role="list" class="flex flex-1 flex-col gap-y-7">
+          <li>
+            <ul role="list" class="-mx-2 space-y-1">
+              {pageList.map(p => {
+                return <li>
+                  {/* <!-- Current: "bg-indigo-700 text-white", Default: 
"text-indigo-200 hover:text-white hover:bg-indigo-700" --> */}
+                  <a href="#" class="bg-indigo-700 text-white group flex 
gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
+                    <img src={p.icon} />
+                    {p.name}
+                  </a>
+                </li>
 
-//       {/* Separator */}
-//       <div class="h-6 w-px bg-gray-900/10 lg:hidden" aria-hidden="true" />
+              })}
+              {/* <li>
+                <a href="#" class="text-indigo-200 hover:text-white 
hover:bg-indigo-700 group flex gap-x-3 rounded-md p-2 text-sm leading-6 
font-semibold">
 
-//       <div class="flex flex-1 gap-x-4 self-stretch lg:gap-x-6">
-//         <div class="relative flex flex-1" />
-//         {/* <form class="relative flex flex-1" action="#" method="GET">
-//           <label htmlFor="search-field" class="sr-only">
-//             Search
-//           </label>
-//           <MagnifyingGlassIcon
-//             class="pointer-events-none absolute inset-y-0 left-0 h-full w-5 
text-gray-400"
-//             aria-hidden="true"
-//           />
-//           <input
-//             id="search-field"
-//             class="block h-full w-full border-0 py-0 pl-8 pr-0 
text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm"
-//             placeholder="Search..."
-//             type="search"
-//             name="search"
-//           />
-//         </form> */}
-//         <div class="flex items-center gap-x-4 lg:gap-x-6">
-//           {/* <button
-//             type="button"
-//             class="-m-2.5 p-2.5 text-gray-400 hover:text-gray-500"
-//           >
-//             <span class="sr-only">View notifications</span>
-//             <BellIcon class="h-6 w-6" aria-hidden="true" />
-//           </button> */}
+                  <i18n.Translate>Officer</i18n.Translate>
+                </a>
+              </li> */}
+            </ul>
+          </li>
 
-//           {/* Separator */}
-//           <div
-//             class="hidden lg:block lg:h-6 lg:w-px lg:bg-gray-900/10"
-//             aria-hidden="true"
-//           />
+          {/* <li class="mt-auto ">
+            <a href="#" class="group -mx-2 flex gap-x-3 rounded-md p-2 text-sm 
font-semibold leading-6 text-indigo-200 hover:bg-indigo-700 hover:text-white">
+              <svg class="h-6 w-6 shrink-0 text-indigo-200 
group-hover:text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5" 
stroke="currentColor" aria-hidden="true">
+                <path stroke-linecap="round" stroke-linejoin="round" d="M9.594 
3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 
1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 
1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 
1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 
.255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 
2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.12 [...]
+                <path stroke-linecap="round" stroke-linejoin="round" d="M15 
12a3 3 0 11-6 0 3 3 0 016 0z" />
+              </svg>
+              Settings
+            </a>
+          </li> */}
 
-//           {/* {officerName === undefined ? (
-//             <div />
-//           ) : (
-//             <Menu
-//               as="div"
-//               class="relative"
-//             >
-//               <Menu.Button class="-m-1.5 flex items-center p-1.5">
-//                 <span class="sr-only">Open user menu</span>
-//                 <img
-//                   class="h-8 w-8 rounded-full bg-gray-50"
-//                   
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80";
-//                   alt=""
-//                 />
-//                 <span class="hidden lg:flex lg:items-center">
-//                   <span
-//                     class="ml-4 text-sm font-semibold leading-6 
text-gray-900"
-//                     aria-hidden="true"
-//                   >
-//                     {officerName}
-//                   </span>
-//                   <ChevronDownIcon
-//                     class="ml-2 h-5 w-5 text-gray-400"
-//                     aria-hidden="true"
-//                   />
-//                 </span>
-//               </Menu.Button>
-//               <Transition
-//                 as={Fragment}
-//                 enter="transition ease-out duration-100"
-//                 enterFrom="transform opacity-0 scale-95"
-//                 enterTo="transform opacity-100 scale-100"
-//                 leave="transition ease-in duration-75"
-//                 leaveFrom="transform opacity-100 scale-100"
-//                 leaveTo="transform opacity-0 scale-95"
-//               >
-//                 <Menu.Items class="absolute right-0 z-10 mt-2.5 w-48 
origin-top-right rounded-md bg-white py-2 shadow-lg ring-1 ring-gray-900/5 
focus:outline-none">
-//                   <Menu.Item>
-//                     {({ active }: { active: boolean }) => (
-//                       <a
-//                         onClick={() => {
-//                           officer.reset();
-//                           password.reset();
-//                         }}
-//                         class={classNames(
-//                           active ? "bg-gray-50" : "",
-//                           "block px-3 py-1 text-sm leading-6 text-gray-900",
-//                         )}
-//                       >
-//                         Forget account
-//                       </a>
-//                     )}
-//                   </Menu.Item>
-//                 </Menu.Items>
-//               </Transition>
-//             </Menu>
-//           )} */}
-//         </div>
-//       </div>
-//     </div>
-//   );
-// }
+        </ul>
+      </nav>
+    </div>
+  )
 
-function Footer() {
-  return (
-    <footer class="absolute bottom-4">
-      <div class="mt-8 md:order-1 md:mt-0">
-        <p class="text-xs leading-5 text-gray-300">
-          Taler Systems SA. {versionText}
-        </p>
-      </div>
-    </footer>
-  );
 }
 
-function Notifications() {
-  const ns = useNotifications();
-
-  // useEffect(() => {
-  //   if (ns.length) {
-  //     // remove notifications after some timeout
-  //   }
-  // }, []);
-  {
-    /* <!-- Global notification live region, render this permanently at the 
end of the document --> */
-  }
-  return (
-    <div
-      aria-live="assertive"
-      class="pointer-events-none fixed inset-0 flex items-end px-4 py-6 
sm:items-start sm:p-6 z-50"
-    >
-      <div class="flex w-full flex-col items-center space-y-4 sm:items-end ">
-        {/* <!--
-  Notification panel, dynamically insert this into the live region when it 
needs to be displayed
 
-  Entering: "transform ease-out duration-300 transition"
-    From: "translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
-    To: "translate-y-0 opacity-100 sm:translate-x-0"
-  Leaving: "transition ease-in duration-100"
-    From: "opacity-100"
-    To: "opacity-0"
---> */}
-        {ns.map(({ message, remove }) => {
-          switch (message.type) {
-            case "error": {
-              return (
-                <div class="pointer-events-auto w-full max-w-sm 
overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5 
">
-                  <div class="p-4 ">
-                    <div class="flex items-start ">
-                      <div class="flex-shrink-0">
-                        <XCircleIcon class="h-6 w-6 text-red-400" />
-                      </div>
-                      <div class="ml-3 w-0 flex-1 pt-0.5">
-                        <p class="text-sm font-medium text-gray-900">
-                          {message.title}
-                        </p>
-                        {message.description && (
-                          <p class="mt-1 text-sm text-gray-500">
-                            {message.description}
-                          </p>
-                        )}
-                      </div>
-                      <div class="ml-4 flex flex-shrink-0">
-                        <button
-                          type="button"
-                          onClick={remove}
-                          class="inline-flex rounded-md bg-white text-gray-400 
hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 
focus:ring-offset-2"
-                        >
-                          <span class="sr-only">Close</span>
-                          <svg
-                            class="h-5 w-5"
-                            viewBox="0 0 20 20"
-                            fill="currentColor"
-                            aria-hidden="true"
-                          >
-                            <path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 
10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 
101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
-                          </svg>
-                        </button>
-                      </div>
-                    </div>
-                  </div>
-                </div>
-              );
-            }
-            case "info": {
-              return (
-                <div class="pointer-events-auto w-full max-w-sm 
overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5 
">
-                  <div class="p-4 ">
-                    <div class="flex items-start ">
-                      <div class="flex-shrink-0">
-                        <CheckCircleIcon class="h-6 w-6 text-green-400" />
-                      </div>
-                      <div class="ml-3 w-0 flex-1 pt-0.5">
-                        <p class="text-sm font-medium text-gray-900">
-                          {message.title}
-                        </p>
-                      </div>
-                      <div class="ml-4 flex flex-shrink-0">
-                        <button
-                          type="button"
-                          onClick={remove}
-                          class="inline-flex rounded-md bg-white text-gray-400 
hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 
focus:ring-offset-2"
-                        >
-                          <span class="sr-only">Close</span>
-                          <svg
-                            class="h-5 w-5"
-                            viewBox="0 0 20 20"
-                            fill="currentColor"
-                            aria-hidden="true"
-                          >
-                            <path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 
10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 
101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
-                          </svg>
-                        </button>
-                      </div>
-                    </div>
-                  </div>
-                </div>
-              );
-            }
-          }
-        })}
-      </div>
-    </div>
-  );
-}
diff --git a/packages/aml-backoffice-ui/src/assets/home.svg 
b/packages/aml-backoffice-ui/src/assets/home.svg
new file mode 100644
index 000000000..35f340162
--- /dev/null
+++ b/packages/aml-backoffice-ui/src/assets/home.svg
@@ -0,0 +1,3 @@
+<svg class="h-6 w-6 shrink-0 text-white" fill="none" viewBox="0 0 24 24" 
stroke-width="1.5" stroke="currentColor" aria-hidden="true">
+  <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 
12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 
1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 
1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" 
/>
+</svg>
\ No newline at end of file
diff --git a/packages/aml-backoffice-ui/src/assets/people.svg 
b/packages/aml-backoffice-ui/src/assets/people.svg
new file mode 100644
index 000000000..1dc878b81
--- /dev/null
+++ b/packages/aml-backoffice-ui/src/assets/people.svg
@@ -0,0 +1,3 @@
+<svg class="h-6 w-6 shrink-0 text-indigo-200 group-hover:text-white" 
fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" 
aria-hidden="true">
+  <path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 
0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 
19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 
21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 
6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 
11-5.25 0 2.625 2.625 0 015.25 0z" />
+</svg>
\ No newline at end of file
diff --git a/packages/aml-backoffice-ui/src/hooks/useCases.ts 
b/packages/aml-backoffice-ui/src/hooks/useCases.ts
index 2a133f46d..c4edd9207 100644
--- a/packages/aml-backoffice-ui/src/hooks/useCases.ts
+++ b/packages/aml-backoffice-ui/src/hooks/useCases.ts
@@ -5,7 +5,7 @@ import {
 } from "@gnu-taler/web-util/browser";
 import { AmlExchangeBackend } from "../types.js";
 // FIX default import https://github.com/microsoft/TypeScript/issues/49189
-import { AmountString, OfficerAccount, TalerExchangeApi, 
TalerExchangeResultByMethod, TalerHttpError } from "@gnu-taler/taler-util";
+import { AmountString, OfficerAccount, OperationFail, TalerExchangeApi, 
TalerExchangeResultByMethod, TalerHttpError } from "@gnu-taler/taler-util";
 import _useSWR, { SWRHook } from "swr";
 import { useExchangeApiContext } from "../context/config.js";
 import { useOfficer } from "./useOfficer.js";
@@ -70,6 +70,15 @@ export function useCases(state: AmlExchangeBackend.AmlState) 
{
   };
 
   // const public_accountslist = data?.type !== "ok" ? [] : 
data.body.public_accounts;
+  if (!session) {
+    return {
+      data: {
+        type: "fail",
+        case: "unauthorized",
+        detail: {}
+      } as OperationFail<never>
+    }
+  }
   if (data) {
     if (data.type === "fail") {
       return { data }
diff --git a/packages/demobank-ui/src/hooks/settings.ts 
b/packages/aml-backoffice-ui/src/hooks/useSettings.ts
similarity index 52%
copy from packages/demobank-ui/src/hooks/settings.ts
copy to packages/aml-backoffice-ui/src/hooks/useSettings.ts
index 1e656b3ba..52f6f1614 100644
--- a/packages/demobank-ui/src/hooks/settings.ts
+++ b/packages/aml-backoffice-ui/src/hooks/useSettings.ts
@@ -26,55 +26,30 @@ import {
 import { buildStorageKey, useLocalStorage, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 
 interface Settings {
-  currentWithdrawalOperationId: string | undefined;
-  showWithdrawalSuccess: boolean;
-  showDemoDescription: boolean;
-  showInstallWallet: boolean;
-  maxWithdrawalAmount: number;
-  fastWithdrawal: boolean;
-  showDebugInfo: boolean;
-
+  allowInsecurePassword: boolean;
 }
 
 export function getAllBooleanSettings(): Array<keyof Settings> {
-  return ["fastWithdrawal", "showDebugInfo", "showDemoDescription", 
"showInstallWallet", "showWithdrawalSuccess"]
+  return ["allowInsecurePassword"]
 }
 
 export function getLabelForSetting(k: keyof Settings, i18n: ReturnType<typeof 
useTranslationContext>["i18n"]): TranslatedString {
   switch (k) {
-    case "currentWithdrawalOperationId": return i18n.str`Current withdrawal 
operation`
-    case "maxWithdrawalAmount": return i18n.str`Max withdrawal amount`
-    case "showWithdrawalSuccess": return i18n.str`Show withdrawal confirmation`
-    case "showDemoDescription": return i18n.str`Show demo description`
-    case "showInstallWallet": return i18n.str`Show install wallet first`
-    case "fastWithdrawal": return i18n.str`Use fast withdrawal form`
-    case "showDebugInfo": return i18n.str`Show debug info`
+    case "allowInsecurePassword": return i18n.str`Allow Insecure password`
   }
 }
 
 export const codecForSettings = (): Codec<Settings> =>
   buildCodecForObject<Settings>()
-    .property("currentWithdrawalOperationId", codecOptional(codecForString()))
-    .property("showWithdrawalSuccess", (codecForBoolean()))
-    .property("showDemoDescription", (codecForBoolean()))
-    .property("showInstallWallet", (codecForBoolean()))
-    .property("fastWithdrawal", (codecForBoolean()))
-    .property("showDebugInfo", (codecForBoolean()))
-    .property("maxWithdrawalAmount", codecForNumber())
+    .property("allowInsecurePassword", (codecForBoolean()))
     .build("Settings");
 
 const defaultSettings: Settings = {
-  currentWithdrawalOperationId: undefined,
-  showWithdrawalSuccess: true,
-  showDemoDescription: true,
-  showInstallWallet: true,
-  maxWithdrawalAmount: 25,
-  fastWithdrawal: false,
-  showDebugInfo: false,
+  allowInsecurePassword: false,
 };
 
-const DEMOBANK_SETTINGS_KEY = buildStorageKey(
-  "bank-settings",
+const EXCHANGE_SETTINGS_KEY = buildStorageKey(
+  "exchange-settings",
   codecForSettings(),
 );
 
@@ -83,7 +58,7 @@ export function useSettings(): [
   <T extends keyof Settings>(key: T, value: Settings[T]) => void,
 ] {
   const { value, update } = useLocalStorage(
-    DEMOBANK_SETTINGS_KEY,
+    EXCHANGE_SETTINGS_KEY,
     defaultSettings,
   );
 
diff --git a/packages/aml-backoffice-ui/src/pages.ts 
b/packages/aml-backoffice-ui/src/pages.ts
index e4e16f05f..17ede651d 100644
--- a/packages/aml-backoffice-ui/src/pages.ts
+++ b/packages/aml-backoffice-ui/src/pages.ts
@@ -1,55 +1,51 @@
-import { Home } from "./pages/Home.js";
-import { Settings } from "./pages/Settings.js";
+import { TranslatedString } from "@gnu-taler/taler-util";
 import { AntiMoneyLaunderingForm } from "./pages/AntiMoneyLaunderingForm.js";
-import { Welcome } from "./pages/Welcome.js";
-import { PageEntry, pageDefinition } from "./route.js";
-import { Officer } from "./pages/Officer.js";
-import { Cases } from "./pages/Cases.js";
 import { CaseDetails } from "./pages/CaseDetails.js";
+import { Cases } from "./pages/Cases.js";
 import { NewFormEntry } from "./pages/NewFormEntry.js";
-
-const home: PageEntry = {
-  url: "#/",
-  view: Home,
-};
+import { Officer } from "./pages/Officer.js";
+import { PageEntry, pageDefinition } from "./route.js";
+import homeLogo from "./assets/home.svg";
+import peopleLogo from "./assets/people.svg";
 const cases: PageEntry = {
   url: "#/cases",
   view: Cases,
+  name: "Cases" as TranslatedString,
+  icon: homeLogo,
+};
+
+const officer: PageEntry = {
+  url: "#/officer",
+  view: Officer,
+  name: "Officer" as TranslatedString,
+  icon: peopleLogo,
 };
+
 const account: PageEntry<{ account: string }> = {
   url: pageDefinition("#/account/:account"),
   view: CaseDetails,
+  name: "Account" as TranslatedString,
+  // icon: () => undefined,
 };
 
 const newFormEntry: PageEntry<{ account?: string; type?: string }> = {
   url: pageDefinition("#/account/:account/new/:type?"),
   view: NewFormEntry,
+  name: "New Form" as TranslatedString,
+  // icon: () => undefined,
 };
 
-const settings: PageEntry = {
-  url: "#/settings",
-  view: Settings,
-};
-const officer: PageEntry = {
-  url: "#/officer",
-  view: Officer,
-};
-const welcome: PageEntry<{ asd?: string; name?: string }> = {
-  url: pageDefinition("#/welcome/:name?"),
-  view: Welcome,
-};
 const form: PageEntry<{ number?: string }> = {
   url: pageDefinition("#/form/:number?"),
   view: AntiMoneyLaunderingForm,
+  name: "Form" as TranslatedString,
+  // icon: () => undefined,
 };
 
 export const Pages = {
-  home,
-  info: cases,
+  cases,
   officer,
-  details: account,
-  settings,
-  welcome,
+  account,
   form,
   newFormEntry,
 };
diff --git a/packages/aml-backoffice-ui/src/pages/Cases.tsx 
b/packages/aml-backoffice-ui/src/pages/Cases.tsx
index 5f79db71e..624f2c985 100644
--- a/packages/aml-backoffice-ui/src/pages/Cases.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Cases.tsx
@@ -4,17 +4,16 @@ import { VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { createNewForm } from "../handlers/forms.js";
 import { useCases } from "../hooks/useCases.js";
-import { useOfficer } from "../hooks/useOfficer.js";
 import { Pages } from "../pages.js";
 import { AmlExchangeBackend } from "../types.js";
 import { amlStateConverter } from "./CaseDetails.js";
+import { Officer } from "./Officer.js";
 
 export function Cases() {
   const { i18n } = useTranslationContext();
 
   const form = createNewForm<{ state: AmlExchangeBackend.AmlState }>();
 
-
   const initial = AmlExchangeBackend.AmlState.pending;
   const [stateFilter, setStateFilter] = useState(initial);
 
@@ -23,16 +22,15 @@ export function Cases() {
   if (!list) {
     return <Loading />
   }
-
   if (list instanceof TalerError) {
     return <ErrorLoading error={list} />
   }
 
   if (list.data.type === "fail") {
     switch (list.data.case) {
-      case "unauthorized":
-      case "officer-not-found":
-      case "officer-disabled": return <div />
+      case "unauthorized": return <Officer />
+      case "officer-not-found": return <Officer />
+      case "officer-disabled": return <Officer />
       default: assertUnreachable(list.data)
     }
   }
@@ -116,7 +114,7 @@ export function Cases() {
                           <td class="whitespace-nowrap px-3 py-5 text-sm 
text-gray-500 ">
                             <div class="text-gray-900">
                               <a
-                                href={Pages.details.url({ account: r.h_payto 
})}
+                                href={Pages.account.url({ account: r.h_payto 
})}
                                 class="text-indigo-600 hover:text-indigo-900"
                               >
                                 {r.h_payto}
diff --git a/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx 
b/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx
index 5dcb8b21d..9b8c3c046 100644
--- a/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx
+++ b/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx
@@ -5,6 +5,7 @@ import {
 } from "@gnu-taler/web-util/browser";
 import { VNode, h } from "preact";
 import { createNewForm } from "../handlers/forms.js";
+import { useSettings } from "../hooks/useSettings.js";
 
 export function CreateAccount({
   onNewAccount,
@@ -16,12 +17,13 @@ export function CreateAccount({
     password: string;
     repeat: string;
   }>();
+  const [settings] = useSettings()
 
   return (
     <div class="flex min-h-full flex-col ">
       <div class="sm:mx-auto sm:w-full sm:max-w-md">
         <h2 class="mt-6 text-center text-2xl font-bold leading-9 
tracking-tight text-gray-900">
-          Create account
+          <i18n.Translate>Create account</i18n.Translate>
         </h2>
       </div>
 
@@ -33,29 +35,29 @@ export function CreateAccount({
                 password: {
                   error: !v.password
                     ? i18n.str`required`
-                    : v.password.length < 8
-                    ? i18n.str`should have at least 8 characters`
-                    : !v.password.match(/[a-z]/) && v.password.match(/[A-Z]/)
-                    ? i18n.str`should have lowercase and uppercase characters`
-                    : !v.password.match(/\d/)
-                    ? i18n.str`should have numbers`
-                    : !v.password.match(/[^a-zA-Z\d]/)
-                    ? i18n.str`should have at least one character which is not 
a number or letter`
-                    : undefined,
+                    : settings.allowInsecurePassword
+                      ? undefined
+                      : v.password.length < 8
+                        ? i18n.str`should have at least 8 characters`
+                        : !v.password.match(/[a-z]/) && 
v.password.match(/[A-Z]/)
+                          ? i18n.str`should have lowercase and uppercase 
characters`
+                          : !v.password.match(/\d/)
+                            ? i18n.str`should have numbers`
+                            : !v.password.match(/[^a-zA-Z\d]/)
+                              ? i18n.str`should have at least one character 
which is not a number or letter`
+                              : undefined,
                 },
                 repeat: {
                   error: !v.repeat
                     ? i18n.str`required`
                     : v.repeat !== v.password
-                    ? i18n.str`doesn't match`
-                    : undefined,
+                      ? i18n.str`doesn't match`
+                      : undefined,
                 },
               };
             }}
             onSubmit={async (v, s) => {
-              console.log(v, s);
               const error = s?.password?.error ?? s?.repeat?.error;
-              console.log(error);
               if (error) {
                 notifyError(
                   "Can't create account" as TranslatedString,
@@ -72,7 +74,9 @@ export function CreateAccount({
                 name="password"
                 type="password"
                 help={
-                  "lower and upper case letters, number and special character" 
as TranslatedString
+                  settings.allowInsecurePassword
+                    ? i18n.str`short password are insecure, turn off insecure 
password in settings`
+                    : i18n.str`lower and upper case letters, number and 
special character`
                 }
                 required
               />
@@ -91,7 +95,7 @@ export function CreateAccount({
                 type="submit"
                 class="flex w-full justify-center rounded-md bg-indigo-600 
px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
               >
-                Create
+                <i18n.Translate>Create</i18n.Translate>
               </button>
             </div>
           </Form.Provider>
diff --git a/packages/aml-backoffice-ui/src/pages/HandleAccountNotReady.tsx 
b/packages/aml-backoffice-ui/src/pages/HandleAccountNotReady.tsx
index 05fd0a019..b3d04d97e 100644
--- a/packages/aml-backoffice-ui/src/pages/HandleAccountNotReady.tsx
+++ b/packages/aml-backoffice-ui/src/pages/HandleAccountNotReady.tsx
@@ -30,5 +30,8 @@ export function HandleAccountNotReady({
       />
     );
   }
+  return <div>
+    some
+  </div>
   throw Error(`unexpected account state ${(officer as any).state}`);
 }
diff --git a/packages/aml-backoffice-ui/src/pages/Home.tsx 
b/packages/aml-backoffice-ui/src/pages/Home.tsx
deleted file mode 100644
index 838032d63..000000000
--- a/packages/aml-backoffice-ui/src/pages/Home.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { h } from "preact";
-
-export function Home() {
-  return <div>Home</div>;
-}
diff --git a/packages/aml-backoffice-ui/src/pages/Officer.tsx 
b/packages/aml-backoffice-ui/src/pages/Officer.tsx
index 4af34805a..abada3725 100644
--- a/packages/aml-backoffice-ui/src/pages/Officer.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Officer.tsx
@@ -1,9 +1,11 @@
 import { Fragment, h } from "preact";
 import { useOfficer } from "../hooks/useOfficer.js";
 import { HandleAccountNotReady } from "./HandleAccountNotReady.js";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
 
 export function Officer() {
   const officer = useOfficer();
+  const { i18n } = useTranslationContext()
   if (officer.state !== "ready") {
     return <HandleAccountNotReady officer={officer} />;
   }
@@ -11,7 +13,7 @@ export function Officer() {
   return (
     <div>
       <h1 class="my-2 text-3xl font-bold tracking-tight text-gray-900 ">
-        Public key
+        <i18n.Translate>Public key</i18n.Translate>
       </h1>
       <div class="max-w-xl text-base leading-7 text-gray-700 lg:max-w-lg">
         <p class="mt-6 font-mono break-all">{officer.account.id}</p>
@@ -25,7 +27,7 @@ export function Officer() {
           rel="noreferrer"
           class="m-4 block rounded-md w-fit border-0 px-3 py-2 text-center 
text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700"
         >
-          Request account activation
+          <i18n.Translate>Request account activation</i18n.Translate>
         </a>
       </p>
       <p>
@@ -36,7 +38,7 @@ export function Officer() {
           }}
           class="m-4 block rounded-md border-0 bg-gray-200 px-3 py-2 
text-center text-sm text-black shadow-sm "
         >
-          Lock account
+          <i18n.Translate>Lock account</i18n.Translate>
         </button>
       </p>
       <p>
@@ -47,7 +49,7 @@ export function Officer() {
           }}
           class="m-4 block rounded-md bg-red-600 px-3 py-2 text-center text-sm 
 text-white shadow-sm hover:bg-red-500 "
         >
-          Remove account
+          <i18n.Translate>Forget account</i18n.Translate>
         </button>
       </p>
     </div>
diff --git a/packages/aml-backoffice-ui/src/pages/Settings.tsx 
b/packages/aml-backoffice-ui/src/pages/Settings.tsx
deleted file mode 100644
index ccff3b210..000000000
--- a/packages/aml-backoffice-ui/src/pages/Settings.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { h } from "preact";
-
-export function Settings() {
-  return <div>Settings</div>;
-}
diff --git a/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx 
b/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx
index 83d8767fb..a6570ffcc 100644
--- a/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx
+++ b/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx
@@ -1,5 +1,5 @@
 import { TranslatedString, UnwrapKeyError } from "@gnu-taler/taler-util";
-import { notifyError, notifyInfo } from "@gnu-taler/web-util/browser";
+import { notifyError, notifyInfo, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { VNode, h } from "preact";
 import { createNewForm } from "../handlers/forms.js";
 
@@ -10,6 +10,7 @@ export function UnlockAccount({
   onAccountUnlocked: (password: string) => void;
   onRemoveAccount: () => void;
 }): VNode {
+  const { i18n } = useTranslationContext()
   const Form = createNewForm<{
     password: string;
   }>();
@@ -17,12 +18,12 @@ export function UnlockAccount({
   return (
     <div class="flex min-h-full flex-col ">
       <div class="sm:mx-auto sm:w-full sm:max-w-md">
-        <h2 class="mt-6 text-center text-2xl font-bold leading-9 
tracking-tight text-gray-900">
-          Account locked
-        </h2>
+        <h1 class="mt-6 text-center text-2xl font-bold leading-9 
tracking-tight text-gray-900">
+          <i18n.Translate>Account locked</i18n.Translate>
+        </h1>
         <p class="mt-6 text-lg leading-8 text-gray-600">
-          Your account is normally locked anytime you reload. To unlock type
-          your password again.
+          <i18n.Translate>Your account is normally locked anytime you reload. 
To unlock type
+            your password again.</i18n.Translate>
         </p>
       </div>
 
@@ -30,7 +31,7 @@ export function UnlockAccount({
         <div class="bg-gray-100 px-6 py-6 shadow sm:rounded-lg sm:px-12">
           <Form.Provider
             initialValue={{
-              password: "welcometo.5146",
+              password: "qwe",
             }}
             onSubmit={async (v) => {
               try {
@@ -63,7 +64,7 @@ export function UnlockAccount({
                 type="submit"
                 class="flex w-full justify-center rounded-md bg-indigo-600 
px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
               >
-                Unlock
+                <i18n.Translate>Unlock</i18n.Translate>
               </button>
             </div>
           </Form.Provider>
@@ -75,7 +76,7 @@ export function UnlockAccount({
           }}
           class="m-4 block rounded-md bg-red-600 px-3 py-2 text-center text-sm 
 text-white shadow-sm hover:bg-red-500 "
         >
-          Remove account
+          <i18n.Translate>Forget account</i18n.Translate>
         </button>
       </div>
     </div>
diff --git a/packages/aml-backoffice-ui/src/pages/Welcome.tsx 
b/packages/aml-backoffice-ui/src/pages/Welcome.tsx
deleted file mode 100644
index 433fbcf59..000000000
--- a/packages/aml-backoffice-ui/src/pages/Welcome.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { h } from "preact";
-
-export function Welcome({ name, asd }: { asd?: string; name?: string }) {
-  return (
-    <div>
-      {asd} Hello {name}
-    </div>
-  );
-}
diff --git a/packages/aml-backoffice-ui/src/route.ts 
b/packages/aml-backoffice-ui/src/route.ts
index d54f9be83..4c3331668 100644
--- a/packages/aml-backoffice-ui/src/route.ts
+++ b/packages/aml-backoffice-ui/src/route.ts
@@ -1,3 +1,4 @@
+import { TranslatedString } from "@gnu-taler/taler-util";
 import { createHashHistory } from "history";
 import { h as create, VNode } from "preact";
 import { useEffect, useState } from "preact/hooks";
@@ -45,14 +46,18 @@ export function pageDefinition<T extends Record<string, 
string>>(
 
 export type PageEntry<T = unknown> = T extends Record<string, string>
   ? {
-      url: PageDefinition<T>;
-      view: (props: T) => VNode;
-    }
+    url: PageDefinition<T>;
+    view: (props: T) => VNode;
+    name: TranslatedString,
+    icon?: string,
+  }
   : T extends unknown
   ? {
-      url: string;
-      view: (props: {}) => VNode;
-    }
+    url: string;
+    view: (props: {}) => VNode;
+    name: TranslatedString,
+    icon?: string,
+  }
   : never;
 
 export function Router({
diff --git a/packages/aml-backoffice-ui/src/settings.ts 
b/packages/aml-backoffice-ui/src/settings.ts
index 2897874a2..9c65837c3 100644
--- a/packages/aml-backoffice-ui/src/settings.ts
+++ b/packages/aml-backoffice-ui/src/settings.ts
@@ -24,7 +24,7 @@ export interface UiSettings {
  * Global settings for the UI.
  */
 const defaultSettings: UiSettings = {
-  backendBaseURL: "https://exchange.demo.taler.net/";,
+  backendBaseURL: "http://exchange.taler.test:1180/";,
   allowRegistrations: true,
   uiName: "Taler Bank",
 };
diff --git a/packages/aml-backoffice-ui/src/utils/Loading.tsx 
b/packages/aml-backoffice-ui/src/utils/Loading.tsx
deleted file mode 100644
index 7cbdad681..000000000
--- a/packages/aml-backoffice-ui/src/utils/Loading.tsx
+++ /dev/null
@@ -1,43 +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 { h, VNode } from "preact";
-
-export function Loading(): VNode {
-  return (
-    <div
-      class="columns is-centered is-vcentered"
-      style={{
-        height: "calc(100% - 3rem)",
-        position: "absolute",
-        width: "100%",
-      }}
-    >
-      <Spinner />
-    </div>
-  );
-}
-
-export function Spinner(): VNode {
-  return (
-    <div class="lds-ring">
-      <div />
-      <div />
-      <div />
-      <div />
-    </div>
-  );
-}
diff --git a/packages/aml-backoffice-ui/tailwind.config.js 
b/packages/aml-backoffice-ui/tailwind.config.js
index 01f058b2e..ec51dfbb8 100644
--- a/packages/aml-backoffice-ui/tailwind.config.js
+++ b/packages/aml-backoffice-ui/tailwind.config.js
@@ -1,6 +1,12 @@
 /** @type {import('tailwindcss').Config} */
 export default {
-  content: ["./src/**/*.{html,tsx}"],
+  content: {
+    relative: true,
+    files: [
+      "./src/**/*.{html,tsx}",
+      "./node_modules/@gnu-taler/web-util/src/**/*.{html,tsx}"
+    ],
+  },
   theme: {
     extend: {},
   },
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx 
b/packages/demobank-ui/src/pages/BankFrame.tsx
index 5561d7b42..f54b049e8 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -15,10 +15,9 @@
  */
 
 import { Amounts, TalerError, TranslatedString } from "@gnu-taler/taler-util";
-import { Attention, LangSelector, Loading, notifyError, notifyException, 
useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { Footer, GlobalNotificationsBanner, Header, Loading, notifyError, 
notifyException, useTranslationContext } from "@gnu-taler/web-util/browser";
 import { ComponentChildren, Fragment, VNode, h } from "preact";
-import { useEffect, useErrorBoundary, useState } from "preact/hooks";
-import logo from "../assets/logo-2021.svg";
+import { useEffect, useErrorBoundary } from "preact/hooks";
 import { useAccountDetails } from "../hooks/access.js";
 import { useBackendState } from "../hooks/backend.js";
 import { getAllBooleanSettings, getLabelForSetting, useSettings } from 
"../hooks/settings.js";
@@ -28,14 +27,6 @@ import { RenderAmount } from "./PaytoWireTransferForm.js";
 const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : 
undefined;
 const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined;
 
-const versionText = VERSION
-  ? GIT_HASH
-    ? <a href={`https://git.taler.net/wallet-core.git/tree/?id=${GIT_HASH}`} 
target="_blank" rel="noreferrer noopener">
-      Version {VERSION} ({GIT_HASH.substring(0, 8)})
-    </a>
-    : VERSION
-  : "";
-
 
 export function BankFrame({
   children,
@@ -47,7 +38,6 @@ export function BankFrame({
   const { i18n } = useTranslationContext();
   const backend = useBackendState();
   const [settings, updateSettings] = useSettings();
-  const [open, setOpen] = useState(false)
 
   const [error, resetError] = useErrorBoundary();
 
@@ -63,188 +53,60 @@ export function BankFrame({
     }
   }, [error])
 
-  const demo_sites = [];
-  if (bankUiSettings.demoSites) {
-    for (const i in bankUiSettings.demoSites)
-      demo_sites.push(
-        <a href={bankUiSettings.demoSites[i][1]}>
-          {bankUiSettings.demoSites[i][0]}
-        </a>,
-      );
-  }
+  return (<div class="min-h-full flex flex-col m-0 bg-slate-200" 
style="min-height: 100vh;">
 
-  return (<div class="min-h-full flex flex-col m-0" style="min-height: 100vh;">
     <div class="bg-indigo-600 pb-32">
-      <nav class="">
-        <div class="mx-auto max-w-7xl px-2 ">
-          <div class="relative flex h-16 items-center justify-between ">
-            <div class="flex items-center px-2">
-              <div class="flex-shrink-0 bg-white rounded-lg">
-                <a href={bankUiSettings.iconLinkURL ?? "#"}>
-                  <img
-                    class="h-8 w-auto"
-                    src={logo}
-                    alt="Taler"
-                    style={{ height: "1.5rem", margin: ".5rem" }}
-                  />
-                </a>
-              </div>
-              {bankUiSettings.demoSites &&
-                <div class="hidden sm:block ml-6 ">
-                  <div class="flex space-x-4">
-                    {/* <!-- Current: "bg-indigo-700 text-white", Default: 
"text-white hover:bg-indigo-500 hover:bg-opacity-75" --> */}
-                    {bankUiSettings.demoSites.map(([name, url]) => {
-                      return <a href={url} class="text-white 
hover:bg-indigo-500 hover:bg-opacity-75 rounded-md py-2 px-3 text-sm 
font-medium">{name}</a>
-                    })}
-                  </div>
-                </div>
-              }
-            </div>
-
-            <div class="flex">
-              <button type="button" class="relative inline-flex items-center 
justify-center rounded-md bg-indigo-600 p-1 text-indigo-200 hover:bg-indigo-500 
hover:bg-opacity-75 hover:text-white focus:outline-none focus:ring-2 
focus:ring-white focus:ring-offset-2 focus:ring-offset-indigo-600" 
aria-controls="mobile-menu" aria-expanded="false"
-                onClick={(e) => {
-                  setOpen(!open)
-                }}>
-                <span class="absolute -inset-0.5"></span>
-                <span class="sr-only">Open settings</span>
-                <svg class="block h-10 w-10" fill="none" viewBox="0 0 24 24" 
stroke-width="2" stroke="currentColor" aria-hidden="true">
-                  <path stroke-linecap="round" stroke-linejoin="round" 
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
-                </svg>
-              </button>
-            </div>
+      <Header
+        title="Bank"
+        iconLinkURL={bankUiSettings.iconLinkURL ?? "#"}
+        onLogout={backend.state.status !== "loggedIn" ? undefined : () => {
+          backend.logOut()
+          updateSettings("currentWithdrawalOperationId", undefined);
+        }}
+        sites={bankUiSettings.demoSites ?? []}
+        supportedLangs={["en", "es", "de"]}
+      >
+        <li>
+          <div class="text-xs font-semibold leading-6 text-gray-400">
+            <i18n.Translate>Preferences</i18n.Translate>
           </div>
-        </div>
-
-        {open &&
-          <div class="relative z-10" aria-labelledby="slide-over-title" 
role="dialog" aria-modal="true"
-            onClick={() => {
-              setOpen(false)
-            }}>
-            <div class="fixed inset-0"></div>
-
-            <div class="fixed inset-0 overflow-hidden">
-              <div class="absolute inset-0 overflow-hidden">
-                <div class="pointer-events-none fixed inset-y-0 right-0 flex 
max-w-full pl-10">
-                  <div class="pointer-events-auto w-screen max-w-md" >
-                    <div class="flex h-full flex-col overflow-y-scroll 
bg-white py-6 shadow-xl" onClick={(e) => {
-                      //do not trigger close if clicking inside the sidebar
-                      e.stopPropagation();
-                    }}>
-                      <div class="px-4 sm:px-6" >
-                        <div class="flex items-start justify-between" >
-                          <h2 class="text-base font-semibold leading-6 
text-gray-900" id="slide-over-title">
-                            <i18n.Translate>Menu</i18n.Translate>
-                          </h2>
-                          <div class="ml-3 flex h-7 items-center">
-                            <button type="button" class="relative rounded-md 
bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 
focus:ring-indigo-500 focus:ring-offset-2"
-                              onClick={(e) => {
-                                setOpen(false)
-                              }}
-
-                            >
-                              <span class="absolute -inset-2.5"></span>
-                              <span class="sr-only">
-                                <i18n.Translate>Close panel</i18n.Translate>
-                              </span>
-                              <svg class="h-6 w-6" fill="none" viewBox="0 0 24 
24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
-                                <path stroke-linecap="round" 
stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
-                              </svg>
-                            </button>
-                          </div>
-                        </div>
-                      </div>
-                      <div class="relative mt-6 flex-1 px-4 sm:px-6">
-                        <nav class="flex flex-1 flex-col" aria-label="Sidebar">
-                          <ul role="list" class="flex flex-1 flex-col gap-y-7">
-                            {backend.state.status === "loggedIn" ?
-                              <li>
-                                <a href="#"
-                                  class="text-gray-700 hover:text-indigo-600 
hover:bg-gray-100 group flex gap-x-3 rounded-md p-2 text-sm leading-6 
font-semibold"
-                                  onClick={() => {
-                                    backend.logOut();
-                                    setOpen(false)
-                                    
updateSettings("currentWithdrawalOperationId", undefined);
-                                  }}
-                                >
-                                  <svg class="h-6 w-6 shrink-0 
text-indigo-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" 
stroke="currentColor" aria-hidden="true">
-                                    <path stroke-linecap="round" 
stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 
0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 
1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 
1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
-                                  </svg>
-                                  <i18n.Translate>Log out</i18n.Translate>
-                                </a>
-                              </li>
-                              : undefined}
-                            <li>
-                              <LangSelector supportedLangs={["en", "es", 
"de"]} />
-                            </li>
-                            <li>
-                              <div class="text-xs font-semibold leading-6 
text-gray-400">
-                                <i18n.Translate>Preferences</i18n.Translate>
-                              </div>
-                              <ul role="list" class="space-y-1">
-                                {getAllBooleanSettings().map(set => {
-                                  const isOn: boolean = !!settings[set]
-                                  return <li class="mt-2 pl-2">
-                                    <div class="flex items-center 
justify-between">
-                                      <span class="flex flex-grow flex-col">
-                                        <span class="text-sm text-black 
font-medium leading-6 " id="availability-label">
-                                          {getLabelForSetting(set, i18n)}
-                                        </span>
-                                      </span>
-                                      <button type="button" 
data-enabled={isOn} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 
relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full 
border-2 border-transparent transition-colors duration-200 ease-in-out 
focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" 
role="switch" aria-checked="false" aria-labelledby="availability-label" 
aria-describedby="availability-description"
-
-                                        onClick={() => { updateSettings(set, 
!isOn); }}>
-                                        <span aria-hidden="true" 
data-enabled={isOn} class="translate-x-5 data-[enabled=false]:translate-x-0 
pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow 
ring-0 transition duration-200 ease-in-out"></span>
-                                      </button>
-                                    </div>
-                                  </li>
-                                })}
-                              </ul>
-                            </li>
-                            {bankUiSettings.demoSites &&
-                              <li class="sm:hidden">
-                                <div class="text-xs font-semibold leading-6 
text-gray-400">
-                                  <i18n.Translate>Sites</i18n.Translate>
-                                </div>
-                                <ul role="list" class="space-y-1">
-                                  {bankUiSettings.demoSites.map(([name, url]) 
=> {
-                                    return <li>
-                                      <a href={url} target="_blank" 
rel="noopener noreferrer" class="text-gray-700 hover:text-indigo-600 
hover:bg-gray-100 group flex gap-x-3 rounded-md p-2 text-sm leading-6 
font-semibold">
-                                        <span class="flex h-6 w-6 shrink-0 
items-center justify-center rounded-lg border text-[0.625rem] font-medium 
bg-white text-gray-400 border-gray-200 group-hover:border-indigo-600 
group-hover:text-indigo-600">&gt;</span>
-                                        <span class="truncate">{name}</span>
-                                      </a>
-                                    </li>
-                                  })}
-                                </ul>
-                              </li>
-                            }
-                          </ul>
-                        </nav>
-                      </div>
-                    </div>
-                  </div>
+          <ul role="list" class="space-y-1">
+            {getAllBooleanSettings().map(set => {
+              const isOn: boolean = !!settings[set]
+              return <li class="mt-2 pl-2">
+                <div class="flex items-center justify-between">
+                  <span class="flex flex-grow flex-col">
+                    <span class="text-sm text-black font-medium leading-6 " 
id="availability-label">
+                      {getLabelForSetting(set, i18n)}
+                    </span>
+                  </span>
+                  <button type="button" data-enabled={isOn} 
class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 
w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent 
transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 
focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-description"
+
+                    onClick={() => { updateSettings(set, !isOn); }}>
+                    <span aria-hidden="true" data-enabled={isOn} 
class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none 
inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition 
duration-200 ease-in-out"></span>
+                  </button>
                 </div>
-              </div>
-            </div>
-          </div>
-        }
-      </nav >
+              </li>
+            })}
+          </ul>
+        </li>
+      </Header>
+    </div >
 
+    <GlobalNotificationsBanner />
+
+    <main class="-mt-32 flex-1">
       {account &&
-        <header class="py-5 border-t border-indigo-300 border-opacity-25 
bg-indigo-600 lg:border-t lg:border-indigo-400 lg:border-opacity-25">
+        <header class="py-5 bg-indigo-600   ">
           <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
-            <div class=" flex flex-wrap items-center justify-between 
sm:flex-nowrap">
-              <h3 class="text-2xl font-bold tracking-tight 
text-white"><WelcomeAccount account={account} /></h3>
-              <h3 class="text-2xl font-bold tracking-tight 
text-white"><AccountBalance account={account} /></h3>
-            </div>
+            <h1 class=" flex flex-wrap items-center justify-between 
sm:flex-nowrap">
+              <span class="text-2xl font-bold tracking-tight 
text-white"><WelcomeAccount account={account} /></span>
+              <span class="text-2xl font-bold tracking-tight 
text-white"><AccountBalance account={account} /></span>
+            </h1>
           </div>
-
         </header>
       }
-    </div >
 
-    <StatusBanner />
-    <main class="-mt-32 flex-1">
       <div class="mx-auto max-w-7xl px-4 pb-12 sm:px-6 lg:px-8">
         <div class="rounded-lg bg-white px-5 py-6 shadow sm:px-6">
           {children}
@@ -252,7 +114,12 @@ export function BankFrame({
       </div>
     </main>
 
-    <Footer />
+    <Footer
+      testingUrl={localStorage.getItem("bank-base-url") ?? undefined}
+      GIT_HASH={GIT_HASH}
+      VERSION={VERSION}
+    />
+
   </div >
 
   );
@@ -269,75 +136,6 @@ function MaybeShowDebugInfo({ info }: { info: any }): 
VNode {
 }
 
 
-function StatusBanner(): VNode {
-  const notifs = useNotifications()
-  if (notifs.length === 0) return <Fragment />
-  return <div class="fixed z-20 w-full p-4"> {
-    notifs.map(n => {
-      switch (n.message.type) {
-        case "error":
-          return <Attention type="danger" title={n.message.title} onClose={() 
=> {
-            n.remove()
-          }}>
-            {n.message.description &&
-              <div class="mt-2 text-sm text-red-700">
-                {n.message.description}
-              </div>
-            }
-            <MaybeShowDebugInfo info={n.message.debug} />
-          </Attention>
-        case "info":
-          return <Attention type="success" title={n.message.title} onClose={() 
=> {
-            n.remove();
-          }} />
-      }
-    })}
-  </div>
-
-}
-
-function TestingTag(): VNode {
-  const testingUrl = localStorage.getItem("bank-base-url");
-  if (!testingUrl) return <Fragment />;
-  return (
-    <p class="text-xs leading-5 text-gray-300">
-      Testing with {testingUrl}{" "}
-      <a
-        href=""
-        onClick={(e) => {
-          e.preventDefault();
-          localStorage.removeItem("bank-base-url");
-          window.location.reload();
-        }}
-      >
-        stop testing
-      </a>
-    </p>
-  );
-}
-
-function Footer() {
-  const { i18n } = useTranslationContext()
-  return (
-    <footer class="bottom-4 mb-4">
-      <div class="mt-8 mx-8 md:order-1 md:mt-0">
-        <div>
-          <p class="text-xs leading-5 text-gray-400">
-            <i18n.Translate>
-              Learn more about <a target="_blank" rel="noreferrer noopener" 
class="font-semibold text-gray-500 hover:text-gray-400" 
href="https://taler.net";>GNU Taler</a>
-            </i18n.Translate>
-          </p>
-        </div>
-        <div style="flex-grow:1" />
-        <p class="text-xs leading-5 text-gray-400">
-          Copyright &copy; 2014&mdash;2023 Taler Systems SA. {versionText}{" "}
-          <TestingTag />
-        </p>
-      </div>
-    </footer>
-  );
-}
-
 function WelcomeAccount({ account: accountName }: { account: string }): VNode {
   const { i18n } = useTranslationContext();
   return <a href="#/my-profile" class="underline underline-offset-2">
diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx 
b/packages/demobank-ui/src/pages/LoginForm.tsx
index 707c1e688..57b0b41c5 100644
--- a/packages/demobank-ui/src/pages/LoginForm.tsx
+++ b/packages/demobank-ui/src/pages/LoginForm.tsx
@@ -26,7 +26,7 @@ import { undefinedIfEmpty, withRuntimeErrorHandling } from 
"../utils.js";
 import { assertUnreachable } from "./WithdrawalOperationPage.js";
 import { doAutoFocus } from "./PaytoWireTransferForm.js";
 import { Attention } from "@gnu-taler/web-util/browser";
-import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
+import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
 
 
 /**
@@ -109,8 +109,8 @@ export function LoginForm({ reason, onRegister }: { 
reason?: "not-found" | "forb
   }
 
   return (
-    <div class="flex min-h-full flex-col justify-center">
-      <ShowLocalNotification notification={notification} />
+    <div class="flex min-h-full flex-col justify-center ">
+      <LocalNotificationBanner notification={notification} />
       <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
         <form class="space-y-6" noValidate
           onSubmit={(e) => {
diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx 
b/packages/demobank-ui/src/pages/OperationState/views.tsx
index 2c4019de2..916a2bd98 100644
--- a/packages/demobank-ui/src/pages/OperationState/views.tsx
+++ b/packages/demobank-ui/src/pages/OperationState/views.tsx
@@ -23,7 +23,7 @@ import { ShowInputErrorLabel } from 
"@gnu-taler/web-util/browser";
 import { useSettings } from "../../hooks/settings.js";
 import { undefinedIfEmpty } from "../../utils.js";
 import { State } from "./index.js";
-import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
+import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
 import { ErrorLoading } from "@gnu-taler/web-util/browser";
 import { Attention } from "@gnu-taler/web-util/browser";
 import { assertUnreachable } from "../WithdrawalOperationPage.js";
@@ -142,7 +142,7 @@ export function NeedConfirmationView({ error, onAbort: 
doAbort, onConfirm: doCon
 
   return (
     <div class="bg-white shadow sm:rounded-lg">
-      <ShowLocalNotification notification={notification} />
+      <LocalNotificationBanner notification={notification} />
       <div class="px-4 py-5 sm:p-6">
         <h3 class="text-base font-semibold text-gray-900">
           <i18n.Translate>Confirm the withdrawal operation</i18n.Translate>
@@ -463,7 +463,7 @@ export function ReadyView({ uri, onClose: doClose }: 
State.Ready): VNode<{}> {
   }
 
   return <Fragment>
-    <ShowLocalNotification notification={notification} />
+    <LocalNotificationBanner notification={notification} />
 
     <div class="flex justify-end mt-4">
       <button type="button"
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx 
b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index 55eba423c..4f7b25f6d 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -41,7 +41,7 @@ import {
   withRuntimeErrorHandling
 } from "../utils.js";
 import { assertUnreachable } from "./WithdrawalOperationPage.js";
-import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
+import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
 
 const logger = new Logger("PaytoWireTransferForm");
 
@@ -390,7 +390,7 @@ export function PaytoWireTransferForm({
           <i18n.Translate>Send</i18n.Translate>
         </button>
       </div>
-      <ShowLocalNotification notification={notification} />
+      <LocalNotificationBanner notification={notification} />
     </form>
   </div >
   )
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx 
b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index e8c1a0e6e..f1394b3f6 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -29,7 +29,7 @@ import { QR } from "../components/QR.js";
 import { useBankCoreApiContext } from "../context/config.js";
 import { withRuntimeErrorHandling } from "../utils.js";
 import { assertUnreachable } from "./WithdrawalOperationPage.js";
-import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
+import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
 
 export function QrCodeSection({
   withdrawUri,
@@ -89,7 +89,7 @@ export function QrCodeSection({
 
   return (
     <Fragment>
-      <ShowLocalNotification notification={notification} />
+      <LocalNotificationBanner notification={notification} />
       <div class="bg-white shadow-xl sm:rounded-lg">
         <div class="px-4 py-5 sm:p-6">
           <h3 class="text-base font-semibold leading-6 text-gray-900">
diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx 
b/packages/demobank-ui/src/pages/RegistrationPage.tsx
index e8969afb9..cade4a277 100644
--- a/packages/demobank-ui/src/pages/RegistrationPage.tsx
+++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx
@@ -26,7 +26,7 @@ import { useBackendState } from "../hooks/backend.js";
 import { bankUiSettings } from "../settings.js";
 import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
 import { getRandomPassword, getRandomUsername } from "./rnd.js";
-import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
+import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
 
 const logger = new Logger("RegistrationPage");
 
@@ -185,7 +185,7 @@ function RegistrationForm({ onComplete, onCancel }: { 
onComplete: () => void, on
 
   return (
     <Fragment>
-      <ShowLocalNotification notification={notification} />
+      <LocalNotificationBanner notification={notification} />
 
       <div class="flex min-h-full flex-col justify-center">
         <div class="sm:mx-auto sm:w-full sm:max-w-sm">
diff --git a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx 
b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
index 43fd39205..2e00fdbba 100644
--- a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
+++ b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
@@ -12,7 +12,7 @@ import { LoginForm } from "./LoginForm.js";
 import { ProfileNavigation } from "./ProfileNavigation.js";
 import { assertUnreachable } from "./WithdrawalOperationPage.js";
 import { AccountForm } from "./admin/AccountForm.js";
-import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
+import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
 
 export function ShowAccountDetails({
   account,
@@ -94,7 +94,7 @@ export function ShowAccountDetails({
 
   return (
     <Fragment>
-      <ShowLocalNotification notification={notification} />
+      <LocalNotificationBanner notification={notification} />
       {accountIsTheCurrentUser ?
         <ProfileNavigation current="details" />
         :
diff --git a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx 
b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
index 759182997..47f8a5750 100644
--- a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
+++ b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
@@ -8,7 +8,7 @@ import { undefinedIfEmpty, withRuntimeErrorHandling } from 
"../utils.js";
 import { doAutoFocus } from "./PaytoWireTransferForm.js";
 import { ProfileNavigation } from "./ProfileNavigation.js";
 import { assertUnreachable } from "./WithdrawalOperationPage.js";
-import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
+import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
 
 export function UpdateAccountPassword({
   account: accountName,
@@ -79,7 +79,7 @@ export function UpdateAccountPassword({
 
   return (
     <Fragment>
-      <ShowLocalNotification notification={notification} />
+      <LocalNotificationBanner notification={notification} />
       {accountIsTheCurrentUser ?
         <ProfileNavigation current="credentials" /> :
         <h1 class="text-base font-semibold leading-6 text-gray-900">
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx 
b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
index 9a45e6285..a9a661c25 100644
--- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
+++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
@@ -37,7 +37,7 @@ import { undefinedIfEmpty, withRuntimeErrorHandling } from 
"../utils.js";
 import { OperationState } from "./OperationState/index.js";
 import { InputAmount, doAutoFocus } from "./PaytoWireTransferForm.js";
 import { assertUnreachable } from "./WithdrawalOperationPage.js";
-import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
+import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
 
 const logger = new Logger("WalletWithdrawForm");
 const RefAmount = forwardRef(InputAmount);
@@ -144,7 +144,7 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, 
onCancel, focus }: {
       e.preventDefault()
     }}
   >
-    <ShowLocalNotification notification={notification} />
+    <LocalNotificationBanner notification={notification} />
 
     <div class="px-4 py-6 ">
       <div class="grid max-w-xs grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx 
b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index f34e8a919..0b339030e 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -37,7 +37,7 @@ import { useSettings } from "../hooks/settings.js";
 import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
 import { RenderAmount } from "./PaytoWireTransferForm.js";
 import { assertUnreachable } from "./WithdrawalOperationPage.js";
-import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
+import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
 
 const logger = new Logger("WithdrawalConfirmationQuestion");
 
@@ -167,7 +167,7 @@ export function WithdrawalConfirmationQuestion({
 
   return (
     <Fragment>
-      <ShowLocalNotification notification={notification} />
+      <LocalNotificationBanner notification={notification} />
 
       <div class="bg-white shadow sm:rounded-lg">
         <div class="px-4 py-5 sm:p-6">
diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx 
b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
index 0369a6283..bdec2a2a9 100644
--- a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
@@ -10,7 +10,7 @@ import { withRuntimeErrorHandling } from "../../utils.js";
 import { assertUnreachable } from "../WithdrawalOperationPage.js";
 import { getRandomPassword } from "../rnd.js";
 import { AccountForm, AccountFormData } from "./AccountForm.js";
-import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
+import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
 
 export function CreateNewAccount({
   onCancel,
@@ -99,7 +99,7 @@ export function CreateNewAccount({
 
   return (
     <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 
bg-gray-100 my-4 px-4 pb-4 rounded-lg">
-      <ShowLocalNotification notification={notification} />
+      <LocalNotificationBanner notification={notification} />
 
       <div class="px-4 sm:px-0">
         <h2 class="text-base font-semibold leading-7 text-gray-900">
diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx 
b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
index 01136fdaf..0519d085a 100644
--- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
@@ -13,7 +13,7 @@ import { undefinedIfEmpty } from "../../utils.js";
 import { LoginForm } from "../LoginForm.js";
 import { doAutoFocus } from "../PaytoWireTransferForm.js";
 import { assertUnreachable } from "../WithdrawalOperationPage.js";
-import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
+import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
 
 export function RemoveAccount({
   account,
@@ -112,7 +112,7 @@ export function RemoveAccount({
 
   return (
     <div>
-      <ShowLocalNotification notification={notification} />
+      <LocalNotificationBanner notification={notification} />
 
       <Attention type="warning" title={i18n.str`You are going to remove the 
account`}>
         <i18n.Translate>This step can't be undone.</i18n.Translate>
diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx 
b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
index 735d84847..10be5ec11 100644
--- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx
+++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
@@ -43,7 +43,7 @@ import {
 import { LoginForm } from "../LoginForm.js";
 import { InputAmount } from "../PaytoWireTransferForm.js";
 import { assertUnreachable } from "../WithdrawalOperationPage.js";
-import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
+import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
 
 interface Props {
   account: string;
@@ -177,7 +177,7 @@ export function CreateCashout({
 
   return (
     <div>
-      <ShowLocalNotification notification={notification} />
+      <LocalNotificationBanner notification={notification} />
       <h1>New cashout</h1>
       <form class="pure-form">
         <fieldset>
diff --git a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx 
b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
index 80e585cf5..4646e9220 100644
--- a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
+++ b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
@@ -39,7 +39,7 @@ import {
   withRuntimeErrorHandling
 } from "../../utils.js";
 import { assertUnreachable } from "../WithdrawalOperationPage.js";
-import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
+import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
 
 interface Props {
   id: string;
@@ -78,7 +78,7 @@ export function ShowCashoutDetails({
   const isPending = String(result.body.status).toUpperCase() === "PENDING";
   return (
     <div>
-      <ShowLocalNotification notification={notification} />
+      <LocalNotificationBanner notification={notification} />
       <h1>Cashout details {id}</h1>
       <form class="pure-form">
         <fieldset>
diff --git a/packages/aml-backoffice-ui/src/assets/logo-2021.svg 
b/packages/web-util/src/assets/logo-2021.svg
similarity index 100%
copy from packages/aml-backoffice-ui/src/assets/logo-2021.svg
copy to packages/web-util/src/assets/logo-2021.svg
diff --git a/packages/demobank-ui/src/assets/logo-white.svg 
b/packages/web-util/src/assets/logo-white.svg
similarity index 100%
copy from packages/demobank-ui/src/assets/logo-white.svg
copy to packages/web-util/src/assets/logo-white.svg
diff --git a/packages/web-util/src/components/Footer.tsx 
b/packages/web-util/src/components/Footer.tsx
new file mode 100644
index 000000000..8fdf6b8f0
--- /dev/null
+++ b/packages/web-util/src/components/Footer.tsx
@@ -0,0 +1,44 @@
+import { useTranslationContext } from "../index.browser.js";
+import { h } from "preact";
+
+export function Footer({ testingUrl, VERSION, GIT_HASH }: { VERSION?: string, 
GIT_HASH?: string, testingUrl?: string }) {
+  const { i18n } = useTranslationContext()
+  const versionText = VERSION
+    ? GIT_HASH
+      ? <a href={`https://git.taler.net/wallet-core.git/tree/?id=${GIT_HASH}`} 
target="_blank" rel="noreferrer noopener">
+        Version {VERSION} ({GIT_HASH.substring(0, 8)})
+      </a>
+      : VERSION
+    : "";
+  return (
+    <footer class="bottom-4 my-4 mx-8 bg-slate-200">
+      <div>
+        <p class="text-xs leading-5 text-gray-400">
+          <i18n.Translate>
+            Learn more about <a target="_blank" rel="noreferrer noopener" 
class="font-semibold text-gray-500 hover:text-gray-400" 
href="https://taler.net";>GNU Taler</a>
+          </i18n.Translate>
+        </p>
+      </div>
+      <div style="flex-grow:1" />
+      <p class="text-xs leading-5 text-gray-400">
+        Copyright &copy; 2014&mdash;2023 Taler Systems SA. {versionText}{" "}
+      </p>
+      {testingUrl &&
+
+        <p class="text-xs leading-5 text-gray-300">
+          Testing with {testingUrl}{" "}
+          <a
+            href=""
+            onClick={(e) => {
+              e.preventDefault();
+              localStorage.removeItem("bank-base-url");
+              window.location.reload();
+            }}
+          >
+            stop testing
+          </a>
+        </p>
+      }
+    </footer>
+  );
+}
diff --git a/packages/web-util/src/components/GlobalNotificationBanner.tsx 
b/packages/web-util/src/components/GlobalNotificationBanner.tsx
new file mode 100644
index 000000000..c8049acc3
--- /dev/null
+++ b/packages/web-util/src/components/GlobalNotificationBanner.tsx
@@ -0,0 +1,28 @@
+import { Fragment, VNode, h } from "preact"
+import { Attention, useNotifications } from "../index.browser.js"
+
+export function GlobalNotificationsBanner(): VNode {
+  const notifs = useNotifications()
+  if (notifs.length === 0) return <Fragment />
+  return <div class="fixed z-20 w-full p-4"> {
+    notifs.map(n => {
+      switch (n.message.type) {
+        case "error":
+          return <Attention type="danger" title={n.message.title} onClose={() 
=> {
+            n.remove()
+          }}>
+            {n.message.description &&
+              <div class="mt-2 text-sm text-red-700">
+                {n.message.description}
+              </div>
+            }
+            {/* <MaybeShowDebugInfo info={n.message.debug} /> */}
+          </Attention>
+        case "info":
+          return <Attention type="success" title={n.message.title} onClose={() 
=> {
+            n.remove();
+          }} />
+      }
+    })}
+  </div>
+}
diff --git a/packages/web-util/src/components/Header.tsx 
b/packages/web-util/src/components/Header.tsx
new file mode 100644
index 000000000..bde0688dc
--- /dev/null
+++ b/packages/web-util/src/components/Header.tsx
@@ -0,0 +1,143 @@
+import { useState } from "preact/hooks";
+import { LangSelector, useTranslationContext } from "../index.browser.js";
+import { ComponentChildren, Fragment, VNode, h } from "preact";
+import logo from "../assets/logo-2021.svg";
+
+export function Header({ title, iconLinkURL, sites, supportedLangs, onLogout, 
children }: { title: string, iconLinkURL: string, children?: ComponentChildren, 
onLogout: (() => void) | undefined, sites: [string, string][], supportedLangs: 
string[] }): VNode {
+  const { i18n } = useTranslationContext();
+  const [open, setOpen] = useState(false)
+  return <Fragment>
+    <header class="bg-indigo-600 w-full mx-auto px-2 border-b 
border-opacity-25 border-indigo-400">
+      <div class="flex flex-row h-16 items-center ">
+        <div class="flex px-2 justify-start">
+          <div class="flex-shrink-0 bg-white rounded-lg">
+            <a href={iconLinkURL ?? "#"}>
+              <img
+                class="h-8 w-auto"
+                src={logo}
+                alt="GNU Taler"
+                style={{ height: "1.5rem", margin: ".5rem" }}
+              />
+            </a>
+          </div>
+          <span class="flex items-center text-white text-lg font-bold ml-4">
+            {title}
+          </span>
+        </div>
+        <div class="block flex-1 ml-6 ">
+          {sites.length !== 0 &&
+            <div class="flex flex-1 space-x-4">
+              {/* <!-- Current: "bg-indigo-700 text-white", Default: 
"text-white hover:bg-indigo-500 hover:bg-opacity-75" --> */}
+              {sites.map(([name, url]) => {
+                return <a href={url} class="text-white hover:bg-indigo-500 
hover:bg-opacity-75 rounded-md py-2 px-3 text-sm font-medium">{name}</a>
+              })}
+            </div>
+          }
+        </div>
+        <div class="flex justify-end">
+          <button type="button" class="relative inline-flex items-center 
justify-center rounded-md bg-indigo-600 p-1 text-indigo-200 hover:bg-indigo-500 
hover:bg-opacity-75 hover:text-white focus:outline-none focus:ring-2 
focus:ring-white focus:ring-offset-2 focus:ring-offset-indigo-600" 
aria-controls="mobile-menu" aria-expanded="false"
+            onClick={(e) => {
+              setOpen(!open)
+            }}>
+            <span class="absolute -inset-0.5"></span>
+            <span class="sr-only"><i18n.Translate>Open 
settings</i18n.Translate></span>
+            <svg class="block h-10 w-10" fill="none" viewBox="0 0 24 24" 
stroke-width="2" stroke="currentColor" aria-hidden="true">
+              <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 
6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
+            </svg>
+          </button>
+        </div>
+      </div>
+    </header>
+
+    {open &&
+      <div class="relative z-10" aria-labelledby="slide-over-title" 
role="dialog" aria-modal="true"
+        onClick={() => {
+          setOpen(false)
+        }}>
+        <div class="fixed inset-0"></div>
+
+        <div class="fixed inset-0 overflow-hidden">
+          <div class="absolute inset-0 overflow-hidden">
+            <div class="pointer-events-none fixed inset-y-0 right-0 flex 
max-w-full pl-10">
+              <div class="pointer-events-auto w-screen max-w-md" >
+                <div class="flex h-full flex-col overflow-y-scroll bg-white 
py-6 shadow-xl" onClick={(e) => {
+                  //do not trigger close if clicking inside the sidebar
+                  e.stopPropagation();
+                }}>
+                  <div class="px-4 sm:px-6" >
+                    <div class="flex items-start justify-between" >
+                      <h2 class="text-base font-semibold leading-6 
text-gray-900" id="slide-over-title">
+                        <i18n.Translate>Menu</i18n.Translate>
+                      </h2>
+                      <div class="ml-3 flex h-7 items-center">
+                        <button type="button" class="relative rounded-md 
bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 
focus:ring-indigo-500 focus:ring-offset-2"
+                          onClick={(e) => {
+                            setOpen(false)
+                          }}
+
+                        >
+                          <span class="absolute -inset-2.5"></span>
+                          <span class="sr-only">
+                            <i18n.Translate>Close panel</i18n.Translate>
+                          </span>
+                          <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" 
stroke-width="1.5" stroke="currentColor" aria-hidden="true">
+                            <path stroke-linecap="round" 
stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
+                          </svg>
+                        </button>
+                      </div>
+                    </div>
+                  </div>
+                  <div class="relative mt-6 flex-1 px-4 sm:px-6">
+                    <nav class="flex flex-1 flex-col" aria-label="Sidebar">
+                      <ul role="list" class="flex flex-1 flex-col gap-y-7">
+                        {onLogout ?
+                          <li>
+                            <a href="#"
+                              class="text-gray-700 hover:text-indigo-600 
hover:bg-gray-100 group flex gap-x-3 rounded-md p-2 text-sm leading-6 
font-semibold"
+                              onClick={() => {
+                                onLogout();
+                                setOpen(false)
+                              }}
+                            >
+                              <svg class="h-6 w-6 shrink-0 text-indigo-600" 
fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" 
aria-hidden="true">
+                                <path stroke-linecap="round" 
stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 
0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 
1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 
1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
+                              </svg>
+                              <i18n.Translate>Log out</i18n.Translate>
+                            </a>
+                          </li>
+                          : undefined}
+                        <li>
+                          <LangSelector supportedLangs={supportedLangs} />
+                        </li>
+                        {/* CHILDREN */}
+                        {children}
+                        {/* /CHILDREN */}
+                        {sites.length !== 0 &&
+                          <li class="sm:hidden">
+                            <div class="text-xs font-semibold leading-6 
text-gray-400">
+                              <i18n.Translate>Sites</i18n.Translate>
+                            </div>
+                            <ul role="list" class="space-y-1">
+                              {sites.map(([name, url]) => {
+                                return <li>
+                                  <a href={url} target="_blank" rel="noopener 
noreferrer" class="text-gray-700 hover:text-indigo-600 hover:bg-gray-100 group 
flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
+                                    <span class="flex h-6 w-6 shrink-0 
items-center justify-center rounded-lg border text-[0.625rem] font-medium 
bg-white text-gray-400 border-gray-200 group-hover:border-indigo-600 
group-hover:text-indigo-600">&gt;</span>
+                                    <span class="truncate">{name}</span>
+                                  </a>
+                                </li>
+                              })}
+                            </ul>
+                          </li>
+                        }
+                      </ul>
+                    </nav>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    }
+  </Fragment>
+}
diff --git a/packages/web-util/src/components/Loading.tsx 
b/packages/web-util/src/components/Loading.tsx
index b567e9056..c5dcd90c1 100644
--- a/packages/web-util/src/components/Loading.tsx
+++ b/packages/web-util/src/components/Loading.tsx
@@ -33,9 +33,9 @@ export function Loading(): VNode {
   );
 }
 
-export function Spinner(): VNode {
+function Spinner(): VNode {
   return (
-    <div class="lds-ring" style={{margin:"auto"}}>
+    <div class="lds-ring" style={{ margin: "auto" }}>
       <div />
       <div />
       <div />
diff --git a/packages/web-util/src/components/ShowLocalNotification.tsx 
b/packages/web-util/src/components/LocalNotificationBanner.tsx
similarity index 93%
rename from packages/web-util/src/components/ShowLocalNotification.tsx
rename to packages/web-util/src/components/LocalNotificationBanner.tsx
index cb947e536..ab46703cb 100644
--- a/packages/web-util/src/components/ShowLocalNotification.tsx
+++ b/packages/web-util/src/components/LocalNotificationBanner.tsx
@@ -3,7 +3,7 @@ import { Attention } from "./Attention.js";
 import { Notification } from "../index.browser.js";
 // import { useSettings } from "../hooks/settings.js";
 
-export function ShowLocalNotification({ notification }: { notification?: 
Notification }): VNode {
+export function LocalNotificationBanner({ notification }: { notification?: 
Notification }): VNode {
   if (!notification) return <Fragment />
   switch (notification.message.type) {
     case "error":
diff --git a/packages/web-util/src/components/index.ts 
b/packages/web-util/src/components/index.ts
index 8344d4a7a..d94502b48 100644
--- a/packages/web-util/src/components/index.ts
+++ b/packages/web-util/src/components/index.ts
@@ -4,5 +4,8 @@ export * from "./CopyButton.js";
 export * from "./ErrorLoading.js";
 export * from "./LangSelector.js";
 export * from "./Loading.js";
+export * from "./Header.js";
+export * from "./Footer.js";
 export * from "./ShowInputErrorLabel.js";
-export * from "./ShowLocalNotification.js";
+export * from "./LocalNotificationBanner.js";
+export * from "./GlobalNotificationBanner.js";

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