gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated (44aeaba7b -> a204105b5)


From: gnunet
Subject: [taler-wallet-core] branch master updated (44aeaba7b -> a204105b5)
Date: Mon, 07 Aug 2023 13:14:50 +0200

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

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

    from 44aeaba7b wallet-core: introduce tiny digits
     new 37d0f9438 accesstoken in memory and better login when switching 
between accounts
     new 8eb0183c7 fix: support for empty strings
     new 7d1621767 ui settings view
     new 9f776d3fb fix date query parameter
     new ef148b150 use stringify taler util
     new 7d53aa275 show simple order creation unless advance mode is selected
     new b1cea84ca show next expiration
     new a204105b5 show advance mode in settings view

The 8 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:
 .../merchant-backoffice-ui/src/Application.tsx     |  24 +-
 .../src/ApplicationReadyRoutes.tsx                 |  14 +-
 .../merchant-backoffice-ui/src/InstanceRoutes.tsx  |   7 +
 .../src/components/exception/login.tsx             |  97 ++++++-
 .../form/{InputBoolean.tsx => InputToggle.tsx}     |  12 +-
 .../src/components/menu/NavigationBar.tsx          |   2 -
 .../src/components/menu/SideBar.tsx                |  64 +++--
 .../src/components/menu/index.tsx                  |  14 +-
 .../merchant-backoffice-ui/src/hooks/backend.ts    |   8 +-
 packages/merchant-backoffice-ui/src/hooks/index.ts |  27 +-
 .../src/hooks/useSettings.ts                       |  21 +-
 .../paths/instance/orders/create/CreatePage.tsx    | 309 +++++++++++----------
 .../paths/instance/orders/details/DetailPage.tsx   |  31 +--
 .../src/paths/instance/orders/details/Timeline.tsx |  20 +-
 .../src/paths/instance/orders/list/ListPage.tsx    |   4 +-
 .../paths/instance/templates/create/CreatePage.tsx |  38 +--
 .../src/paths/instance/templates/qr/QrPage.tsx     |  40 +--
 .../src/paths/notfound/index.tsx                   |   1 -
 .../src/paths/settings/index.tsx                   |  77 +++++
 packages/merchant-backoffice-ui/src/scss/main.scss |  14 +-
 .../src/scss/toggle.scss                           |   0
 packages/taler-util/src/taleruri.ts                |   2 +-
 22 files changed, 526 insertions(+), 300 deletions(-)
 copy packages/merchant-backoffice-ui/src/components/form/{InputBoolean.tsx => 
InputToggle.tsx} (89%)
 copy packages/{taler-wallet-webextension => 
merchant-backoffice-ui}/src/hooks/useSettings.ts (71%)
 create mode 100644 packages/merchant-backoffice-ui/src/paths/settings/index.tsx
 copy packages/{demobank-ui => merchant-backoffice-ui}/src/scss/toggle.scss 
(100%)

diff --git a/packages/merchant-backoffice-ui/src/Application.tsx 
b/packages/merchant-backoffice-ui/src/Application.tsx
index 23510c456..f6a81ff8d 100644
--- a/packages/merchant-backoffice-ui/src/Application.tsx
+++ b/packages/merchant-backoffice-ui/src/Application.tsx
@@ -26,7 +26,7 @@ import {
 } from "@gnu-taler/web-util/browser";
 import { Fragment, h, VNode } from "preact";
 import { route } from "preact-router";
-import { useMemo } from "preact/hooks";
+import { useMemo, useState } from "preact/hooks";
 import { ApplicationReadyRoutes } from "./ApplicationReadyRoutes.js";
 import { Loading } from "./components/exception/loading.js";
 import {
@@ -42,6 +42,7 @@ import { useBackendConfig } from "./hooks/backend.js";
 import { strings } from "./i18n/strings.js";
 import LoginPage from "./paths/login/index.js";
 import { HttpStatusCode } from "@gnu-taler/taler-util";
+import { Settings } from "./paths/settings/index.js";
 
 export function Application(): VNode {
   return (
@@ -70,10 +71,19 @@ function ApplicationStatusRoutes(): VNode {
     : { currency: "unknown", version: "unknown" };
   const ctx = useMemo(() => ({ currency, version }), [currency, version]);
 
+  const [showSettings, setShowSettings] = useState(false)
+
+  if (showSettings) {
+    return <Fragment>
+      <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} 
title="UI Settings" />
+      <Settings />
+    </Fragment>
+  }
+
   if (!triedToLog) {
     return (
       <Fragment>
-        <NotYetReadyAppMenu title="Welcome!" />
+        <NotYetReadyAppMenu title="Welcome!" onShowSettings={() => 
setShowSettings(true)} />
         <LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
       </Fragment>
     );
@@ -87,7 +97,7 @@ function ApplicationStatusRoutes(): VNode {
     ) {
       return (
         <Fragment>
-          <NotYetReadyAppMenu title="Login" />
+          <NotYetReadyAppMenu title="Login" onShowSettings={() => 
setShowSettings(true)} />
           <LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
         </Fragment>
       );
@@ -98,7 +108,7 @@ function ApplicationStatusRoutes(): VNode {
     ) {
       return (
         <Fragment>
-          <NotYetReadyAppMenu title="Error" />
+          <NotYetReadyAppMenu title="Error" onShowSettings={() => 
setShowSettings(true)} />
           <NotificationCard
             notification={{
               message: i18n.str`Server not found`,
@@ -112,7 +122,7 @@ function ApplicationStatusRoutes(): VNode {
     }
     if (result.type === ErrorType.SERVER) {
       <Fragment>
-        <NotYetReadyAppMenu title="Error" />
+        <NotYetReadyAppMenu title="Error" onShowSettings={() => 
setShowSettings(true)} />
         <NotificationCard
           notification={{
             message: i18n.str`Server response with an error code`,
@@ -125,7 +135,7 @@ function ApplicationStatusRoutes(): VNode {
     }
     if (result.type === ErrorType.UNREADABLE) {
       <Fragment>
-        <NotYetReadyAppMenu title="Error" />
+        <NotYetReadyAppMenu title="Error" onShowSettings={() => 
setShowSettings(true)} />
         <NotificationCard
           notification={{
             message: i18n.str`Response from server is unreadable, http status: 
${result.status}`,
@@ -138,7 +148,7 @@ function ApplicationStatusRoutes(): VNode {
     }
     return (
       <Fragment>
-        <NotYetReadyAppMenu title="Error" />
+        <NotYetReadyAppMenu title="Error" onShowSettings={() => 
setShowSettings(true)} />
         <NotificationCard
           notification={{
             message: i18n.str`Unexpected Error`,
diff --git a/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx 
b/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx
index 7731dac88..277c2b176 100644
--- a/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx
+++ b/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx
@@ -33,6 +33,7 @@ import { InstanceRoutes } from "./InstanceRoutes.js";
 import LoginPage from "./paths/login/index.js";
 import { INSTANCE_ID_LOOKUP } from "./utils/constants.js";
 import { HttpStatusCode } from "@gnu-taler/taler-util";
+import { Settings } from "./paths/settings/index.js";
 
 export function ApplicationReadyRoutes(): VNode {
   const { i18n } = useTranslationContext();
@@ -48,8 +49,15 @@ export function ApplicationReadyRoutes(): VNode {
     clearAllTokens();
     route("/");
   };
+  const [showSettings, setShowSettings] = useState(false)
 
-  if (result.loading) return <NotYetReadyAppMenu title="Loading..." />;
+  if (showSettings) {
+    return <Fragment>
+      <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} 
title="UI Settings" onLogout={clearTokenAndGoToRoot} />
+      <Settings/>
+    </Fragment>
+  }
+  if (result.loading) return <NotYetReadyAppMenu onShowSettings={() => 
setShowSettings(true)} title="Loading..." />;
 
   let admin = true;
   let instanceNameByBackendURL;
@@ -61,7 +69,7 @@ export function ApplicationReadyRoutes(): VNode {
     ) {
       return (
         <Fragment>
-          <NotYetReadyAppMenu title="Login" onLogout={clearTokenAndGoToRoot} />
+          <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} 
title="Login" onLogout={clearTokenAndGoToRoot} />
           <NotificationCard
             notification={{
               message: i18n.str`Access denied`,
@@ -81,7 +89,7 @@ export function ApplicationReadyRoutes(): VNode {
       // does not match our pattern
       return (
         <Fragment>
-          <NotYetReadyAppMenu title="Error" onLogout={clearTokenAndGoToRoot} />
+          <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} 
title="Error" onLogout={clearTokenAndGoToRoot} />
           <NotificationCard
             notification={{
               message: i18n.str`Couldn't access the server.`,
diff --git a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx 
b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
index cb4abdd40..1547442ea 100644
--- a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
+++ b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
@@ -68,6 +68,7 @@ import LoginPage from "./paths/login/index.js";
 import NotFoundPage from "./paths/notfound/index.js";
 import { Notification } from "./utils/types.js";
 import { MerchantBackend } from "./declaration.js";
+import { Settings } from "./paths/settings/index.js";
 
 export enum InstancePaths {
   // details = '/',
@@ -100,6 +101,8 @@ export enum InstancePaths {
   webhooks_list = "/webhooks",
   webhooks_update = "/webhooks/:tid/update",
   webhooks_new = "/webhooks/new",
+
+  settings = "/settings",
 }
 
 // eslint-disable-next-line @typescript-eslint/no-empty-function
@@ -240,6 +243,9 @@ export function InstanceRoutes({
       <Menu
         instance={id}
         admin={admin}
+        onShowSettings={() => {
+          route("/settings")
+        }}
         path={path}
         onLogout={clearTokenAndGoToRoot}
         setInstanceName={setInstanceName}
@@ -558,6 +564,7 @@ export function InstanceRoutes({
           }}
         />
         <Route path={InstancePaths.kyc} component={ListKYCPage} />
+        <Route path={InstancePaths.settings} component={Settings} />
         {/**
          * Example pages
          */}
diff --git a/packages/merchant-backoffice-ui/src/components/exception/login.tsx 
b/packages/merchant-backoffice-ui/src/components/exception/login.tsx
index 42c5e89d0..f2f94a7c5 100644
--- a/packages/merchant-backoffice-ui/src/components/exception/login.tsx
+++ b/packages/merchant-backoffice-ui/src/components/exception/login.tsx
@@ -20,7 +20,7 @@
  */
 
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { h, VNode } from "preact";
+import { ComponentChildren, h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { useBackendContext } from "../../context/backend.js";
 import { useInstanceContext } from "../../context/instance.js";
@@ -40,7 +40,7 @@ function getTokenValuePart(t: string): string {
 }
 
 function normalizeToken(r: string): string {
-  return `secret-token:${encodeURIComponent(r)}`;
+  return `secret-token:${r}`;
 }
 
 function cleanUp(s: string): string {
@@ -53,7 +53,7 @@ function cleanUp(s: string): string {
 
 export function LoginModal({ onConfirm, withMessage }: Props): VNode {
   const { url: backendUrl, token: baseToken } = useBackendContext();
-  const { admin, token: instanceToken } = useInstanceContext();
+  const { admin, token: instanceToken, id } = useInstanceContext();
   const testLogin = useCredentialsChecker();
   const currentToken = getTokenValuePart(
     (!admin ? baseToken : instanceToken) ?? "",
@@ -63,6 +63,78 @@ export function LoginModal({ onConfirm, withMessage }: 
Props): VNode {
   const [url, setURL] = useState(cleanUp(backendUrl));
   const { i18n } = useTranslationContext();
 
+  if (admin && id !== "default") {
+    //admin trying to access another instance
+    return (<div class="columns is-centered" style={{ margin: "auto" }}>
+      <div class="column is-two-thirds ">
+        <div class="modal-card" style={{ width: "100%", margin: 0 }}>
+          <header
+            class="modal-card-head"
+            style={{ border: "1px solid", borderBottom: 0 }}
+          >
+            <p class="modal-card-title">{i18n.str`Login required`}</p>
+          </header>
+          <section
+            class="modal-card-body"
+            style={{ border: "1px solid", borderTop: 0, borderBottom: 0 }}
+          >
+            <p>
+              <i18n.Translate>Need the access token for the 
instance.</i18n.Translate>
+            </p>
+            <div class="field is-horizontal">
+              <div class="field-label is-normal">
+                <label class="label">
+                  <i18n.Translate>Access Token</i18n.Translate>
+                </label>
+              </div>
+              <div class="field-body">
+                <div class="field">
+                  <p class="control is-expanded">
+                    <input
+                      class="input"
+                      type="password"
+                      placeholder={"set new access token"}
+                      name="token"
+                      onKeyPress={(e) =>
+                        e.keyCode === 13
+                          ? onConfirm(url, normalizeToken(token))
+                          : null
+                      }
+                      value={token}
+                      onInput={(e): void => setToken(e?.currentTarget.value)}
+                    />
+                  </p>
+                </div>
+              </div>
+            </div>
+          </section>
+          <footer
+            class="modal-card-foot "
+            style={{
+              justifyContent: "flex-end",
+              border: "1px solid",
+              borderTop: 0,
+            }}
+          >
+            <AsyncButton
+              onClick={async () => {
+                const secretToken = normalizeToken(token);
+                const { valid, cause } = await 
testLogin(`${url}/instances/${id}`, secretToken);
+                if (valid) {
+                  onConfirm(url, secretToken);
+                } else {
+                  onConfirm(url);
+                }
+              }}
+            >
+              <i18n.Translate>Confirm</i18n.Translate>
+            </AsyncButton>
+          </footer>
+        </div>
+      </div>
+    </div>)
+  }
+
   return (
     <div class="columns is-centered" style={{ margin: "auto" }}>
       <div class="column is-two-thirds ">
@@ -137,8 +209,7 @@ export function LoginModal({ onConfirm, withMessage }: 
Props): VNode {
               borderTop: 0,
             }}
           >
-            <button
-              class="button is-info"
+            <AsyncButton
               onClick={async () => {
                 const secretToken = normalizeToken(token);
                 const { valid, cause } = await testLogin(url, secretToken);
@@ -150,10 +221,24 @@ export function LoginModal({ onConfirm, withMessage }: 
Props): VNode {
               }}
             >
               <i18n.Translate>Confirm</i18n.Translate>
-            </button>
+            </AsyncButton>
           </footer>
         </div>
       </div>
     </div>
   );
 }
+
+function AsyncButton({ onClick, children }: { onClick: () => Promise<void>, 
children: ComponentChildren }): VNode {
+  const [running, setRunning] = useState(false)
+  return <button class="button is-info" disabled={running} onClick={() => {
+    setRunning(true)
+    onClick().then(() => {
+      setRunning(false)
+    }).catch(() => {
+      setRunning(false)
+    })
+  }}>
+    {children}
+  </button>
+}
diff --git 
a/packages/merchant-backoffice-ui/src/components/form/InputBoolean.tsx 
b/packages/merchant-backoffice-ui/src/components/form/InputToggle.tsx
similarity index 89%
copy from packages/merchant-backoffice-ui/src/components/form/InputBoolean.tsx
copy to packages/merchant-backoffice-ui/src/components/form/InputToggle.tsx
index f79e16c07..61ddf3c84 100644
--- a/packages/merchant-backoffice-ui/src/components/form/InputBoolean.tsx
+++ b/packages/merchant-backoffice-ui/src/components/form/InputToggle.tsx
@@ -33,7 +33,7 @@ interface Props<T> extends InputProps<T> {
 const defaultToBoolean = (f?: any): boolean | undefined => f || "";
 const defaultFromBoolean = (v: boolean | undefined): any => v as any;
 
-export function InputBoolean<T>({
+export function InputToggle<T>({
   name,
   readonly,
   placeholder,
@@ -56,7 +56,7 @@ export function InputBoolean<T>({
   return (
     <div class="field is-horizontal">
       <div class="field-label is-normal">
-        <label class="label">
+        <label class="label" style={{ width: 200 }}>
           {label}
           {tooltip && (
             <span class="icon has-tooltip-right" data-tooltip={tooltip}>
@@ -65,13 +65,13 @@ export function InputBoolean<T>({
           )}
         </label>
       </div>
-      <div class="field-body is-flex-grow-3">
+      <div class="field-body is-flex-grow-1">
         <div class="field">
           <p class={expand ? "control is-expanded" : "control"}>
-            <label class="b-checkbox checkbox">
+            <label class="toggle" style={{ marginLeft: 4, marginTop: 0 }}>
               <input
                 type="checkbox"
-                class={toBoolean(value) === undefined ? "is-indeterminate" : 
""}
+                class={toBoolean(value) === undefined ? "is-indeterminate" : 
"toggle-checkbox"}
                 checked={toBoolean(value)}
                 placeholder={placeholder}
                 readonly={readonly}
@@ -79,7 +79,7 @@ export function InputBoolean<T>({
                 disabled={readonly}
                 onChange={onCheckboxClick}
               />
-              <span class="check" />
+              <div class="toggle-switch"></div>
             </label>
             {help}
           </p>
diff --git 
a/packages/merchant-backoffice-ui/src/components/menu/NavigationBar.tsx 
b/packages/merchant-backoffice-ui/src/components/menu/NavigationBar.tsx
index 9624a2c38..9f1b33893 100644
--- a/packages/merchant-backoffice-ui/src/components/menu/NavigationBar.tsx
+++ b/packages/merchant-backoffice-ui/src/components/menu/NavigationBar.tsx
@@ -20,7 +20,6 @@
  */
 
 import { h, VNode } from "preact";
-import { LangSelector } from "./LangSelector.js";
 import logo from "../../assets/logo-2021.svg";
 
 interface Props {
@@ -65,7 +64,6 @@ export function NavigationBar({ onMobileMenu, title }: 
Props): VNode {
         </a>
         <div class="navbar-end">
           <div class="navbar-item" style={{ paddingTop: 4, paddingBottom: 4 }}>
-            <LangSelector />
           </div>
         </div>
       </div>
diff --git a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx 
b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx
index 6fee600eb..f3cf80b92 100644
--- a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx
+++ b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx
@@ -31,6 +31,7 @@ const VERSION = typeof __VERSION__ !== "undefined" ? 
__VERSION__ : undefined;
 
 interface Props {
   onLogout: () => void;
+  onShowSettings: () => void;
   mobile?: boolean;
   instance: string;
   admin?: boolean;
@@ -40,6 +41,7 @@ interface Props {
 export function Sidebar({
   mobile,
   instance,
+  onShowSettings,
   onLogout,
   admin,
   mimic,
@@ -78,21 +80,8 @@ export function Sidebar({
       <div class="menu is-menu-main">
         {instance ? (
           <Fragment>
-            <p class="menu-label">
-              <i18n.Translate>Instance</i18n.Translate>
-            </p>
             <ul class="menu-list">
-              <li>
-                <a href={"/update"} class="has-icon">
-                  <span class="icon">
-                    <i class="mdi mdi-square-edit-outline" />
-                  </span>
-                  <span class="menu-item-label">
-                    <i18n.Translate>Settings</i18n.Translate>
-                  </span>
-                </a>
-              </li>
-              <li>
+            <li>
                 <a href={"/orders"} class="has-icon">
                   <span class="icon">
                     <i class="mdi mdi-cash-register" />
@@ -132,6 +121,31 @@ export function Sidebar({
                   </span>
                 </a>
               </li>
+              {needKYC && (
+                <li>
+                  <a href={"/kyc"} class="has-icon">
+                    <span class="icon">
+                      <i class="mdi mdi-account-check" />
+                    </span>
+                    <span class="menu-item-label">KYC Status</span>
+                  </a>
+                </li>
+              )}
+            </ul>
+            <p class="menu-label">
+              <i18n.Translate>Configuration</i18n.Translate>
+            </p>
+            <ul class="menu-list">
+              <li>
+                <a href={"/update"} class="has-icon">
+                  <span class="icon">
+                    <i class="mdi mdi-square-edit-outline" />
+                  </span>
+                  <span class="menu-item-label">
+                    <i18n.Translate>Account</i18n.Translate>
+                  </span>
+                </a>
+              </li>
               <li>
                 <a href={"/reserves"} class="has-icon">
                   <span class="icon">
@@ -150,16 +164,6 @@ export function Sidebar({
                   </span>
                 </a>
               </li>
-              {needKYC && (
-                <li>
-                  <a href={"/kyc"} class="has-icon">
-                    <span class="icon">
-                      <i class="mdi mdi-account-check" />
-                    </span>
-                    <span class="menu-item-label">KYC Status</span>
-                  </a>
-                </li>
-              )}
             </ul>
           </Fragment>
         ) : undefined}
@@ -167,6 +171,18 @@ export function Sidebar({
           <i18n.Translate>Connection</i18n.Translate>
         </p>
         <ul class="menu-list">
+          <li>
+            <a class="has-icon is-state-info is-hoverable"
+              onClick={(): void => onShowSettings()}
+              >
+              <span class="icon">
+                <i class="mdi mdi-newspaper" />
+              </span>
+              <span class="menu-item-label">
+                <i18n.Translate>Settings</i18n.Translate>
+              </span>
+            </a>
+          </li>
           <li>
             <div>
               <span style={{ width: "3rem" }} class="icon">
diff --git a/packages/merchant-backoffice-ui/src/components/menu/index.tsx 
b/packages/merchant-backoffice-ui/src/components/menu/index.tsx
index 56573b8ca..cdbae4ae0 100644
--- a/packages/merchant-backoffice-ui/src/components/menu/index.tsx
+++ b/packages/merchant-backoffice-ui/src/components/menu/index.tsx
@@ -75,6 +75,7 @@ interface MenuProps {
   instance: string;
   admin?: boolean;
   onLogout?: () => void;
+  onShowSettings: () => void;
   setInstanceName: (s: string) => void;
 }
 
@@ -93,6 +94,7 @@ function WithTitle({
 
 export function Menu({
   onLogout,
+  onShowSettings,
   title,
   instance,
   path,
@@ -121,6 +123,7 @@ export function Menu({
 
         {onLogout && (
           <Sidebar
+            onShowSettings={onShowSettings}
             onLogout={onLogout}
             admin={admin}
             mimic={mimic}
@@ -130,7 +133,12 @@ export function Menu({
         )}
 
         {mimic && (
-          <nav class="level">
+          <nav class="level" style={{
+            zIndex: 100,
+            position:"fixed",
+            width:"50%",
+            marginLeft: "20%"
+          }}>
             <div class="level-item has-text-centered has-background-warning">
               <p class="is-size-5">
                 You are viewing the instance <b>&quot;{instance}&quot;</b>.{" 
"}
@@ -154,6 +162,7 @@ export function Menu({
 interface NotYetReadyAppMenuProps {
   title: string;
   onLogout?: () => void;
+  onShowSettings: () => void;
 }
 
 interface NotifProps {
@@ -194,6 +203,7 @@ export function NotificationCard({
 
 export function NotYetReadyAppMenu({
   onLogout,
+  onShowSettings,
   title,
 }: NotYetReadyAppMenuProps): VNode {
   const [mobileOpen, setMobileOpen] = useState(false);
@@ -212,7 +222,7 @@ export function NotYetReadyAppMenu({
         title={title}
       />
       {onLogout && (
-        <Sidebar onLogout={onLogout} instance="" mobile={mobileOpen} />
+        <Sidebar onShowSettings={onShowSettings} onLogout={onLogout} 
instance="" mobile={mobileOpen} />
       )}
     </div>
   );
diff --git a/packages/merchant-backoffice-ui/src/hooks/backend.ts 
b/packages/merchant-backoffice-ui/src/hooks/backend.ts
index 90fd320a9..145a366f6 100644
--- a/packages/merchant-backoffice-ui/src/hooks/backend.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/backend.ts
@@ -239,16 +239,16 @@ export function useBackendInstanceRequest(): 
useBackendInstanceRequestType {
       searchDate?: Date,
       delta?: number,
     ): Promise<HttpResponseOk<T>> {
-      const date_ms =
+      const date_s =
         delta && delta < 0 && searchDate
-          ? searchDate.getTime() + 1
-          : searchDate?.getTime();
+          ? (searchDate.getTime() / 1000) + 1
+          : searchDate !== undefined ? (searchDate.getTime() / 1000) : 
undefined;
       const params: any = {};
       if (paid !== undefined) params.paid = paid;
       if (delta !== undefined) params.delta = delta;
       if (refunded !== undefined) params.refunded = refunded;
       if (wired !== undefined) params.wired = wired;
-      if (date_ms !== undefined) params.date_ms = date_ms;
+      if (date_s !== undefined) params.date_s = date_s;
       return requestHandler<T>(baseUrl, endpoint, { params, token });
     },
     [baseUrl, token],
diff --git a/packages/merchant-backoffice-ui/src/hooks/index.ts 
b/packages/merchant-backoffice-ui/src/hooks/index.ts
index 316620cf7..b77b9dea8 100644
--- a/packages/merchant-backoffice-ui/src/hooks/index.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/index.ts
@@ -21,6 +21,7 @@
 
 import { StateUpdater, useCallback, useState } from "preact/hooks";
 import { ValueOrFunction } from "../utils/types.js";
+import { useMemoryStorage } from "@gnu-taler/web-util/browser";
 
 const calculateRootPath = () => {
   const rootPath =
@@ -52,14 +53,17 @@ export function useBackendURL(
 
 export function useBackendDefaultToken(
   initialValue?: string,
-): [string | undefined, StateUpdater<string | undefined>] {
-  return useLocalStorage("backend-token", initialValue);
+): [string | undefined, ((d: string | undefined) => void)] {
+  // uncomment for testing
+  initialValue = "secret-token:secret" as string | undefined
+  const { update, value } = useMemoryStorage(`backend-token`, initialValue)
+  return [value, update];
 }
 
 export function useBackendInstanceToken(
   id: string,
-): [string | undefined, StateUpdater<string | undefined>] {
-  const [token, setToken] = useLocalStorage(`backend-token-${id}`);
+): [string | undefined, ((d: string | undefined) => void)] {
+  const { update: setToken, value: token, reset } = 
useMemoryStorage(`backend-token-${id}`)
   const [defaultToken, defaultSetToken] = useBackendDefaultToken();
 
   // instance named 'default' use the default token
@@ -67,15 +71,16 @@ export function useBackendInstanceToken(
     return [defaultToken, defaultSetToken];
   }
   function updateToken(
-    value:
-      | (string | undefined)
-      | ((s: string | undefined) => string | undefined),
+    value: (string | undefined)
   ): void {
-    setToken((p) => {
-      const toStore = value instanceof Function ? value(p) : value;
-      return toStore;
-    });
+    console.log("seeting token", value)
+    if (value === undefined) {
+      reset()
+    } else {
+      setToken(value)
+    }
   }
+  console.log("token", token)
 
   return [token, updateToken];
 }
diff --git a/packages/taler-wallet-webextension/src/hooks/useSettings.ts 
b/packages/merchant-backoffice-ui/src/hooks/useSettings.ts
similarity index 71%
copy from packages/taler-wallet-webextension/src/hooks/useSettings.ts
copy to packages/merchant-backoffice-ui/src/hooks/useSettings.ts
index 5c1ed7924..5c0932f27 100644
--- a/packages/taler-wallet-webextension/src/hooks/useSettings.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/useSettings.ts
@@ -15,7 +15,6 @@
  */
 
 import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
-import { Settings, defaultSettings } from "../platform/api.js";
 import {
   Codec,
   buildCodecForObject,
@@ -31,20 +30,20 @@ function parse_json_or_undefined<T>(str: string | 
undefined): T | undefined {
   }
 }
 
+export interface Settings {
+  advanceOrderMode: boolean
+}
+
+const defaultSettings: Settings = {
+  advanceOrderMode: false,
+}
+
 export const codecForSettings = (): Codec<Settings> =>
   buildCodecForObject<Settings>()
-    .property("walletAllowHttp", codecForBoolean())
-    .property("walletBatchWithdrawal", codecForBoolean())
-    .property("injectTalerSupport", codecForBoolean())
-    .property("advanceMode", codecForBoolean())
-    .property("backup", codecForBoolean())
-    .property("langSelector", codecForBoolean())
-    .property("showJsonOnError", codecForBoolean())
-    .property("extendedAccountTypes", codecForBoolean())
-    .property("suspendIndividualTransaction", codecForBoolean())
+    .property("advanceOrderMode", codecForBoolean())
     .build("Settings");
 
-const SETTINGS_KEY = buildStorageKey("wallet-settings", codecForSettings());
+const SETTINGS_KEY = buildStorageKey("merchant-settings", codecForSettings());
 
 export function useSettings(): [
   Readonly<Settings>,
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
index c8cc20ae0..fa9347c6e 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
@@ -43,6 +43,7 @@ import { Duration, MerchantBackend, WithId } from 
"../../../../declaration.js";
 import { OrderCreateSchema as schema } from "../../../../schemas/index.js";
 import { rate } from "../../../../utils/amount.js";
 import { undefinedIfEmpty } from "../../../../utils/table.js";
+import { useSettings } from "../../../../hooks/useSettings.js";
 
 interface Props {
   onCreate: (d: MerchantBackend.Orders.PostOrderRequest) => void;
@@ -62,8 +63,8 @@ function with_defaults(config: InstanceConfig): 
Partial<Entity> {
     !config.default_pay_delay || config.default_pay_delay.d_us === "forever"
       ? undefined
       : add(new Date(), {
-          seconds: config.default_pay_delay.d_us / (1000 * 1000),
-        });
+        seconds: config.default_pay_delay.d_us / (1000 * 1000),
+      });
 
   return {
     inventoryProducts: {},
@@ -138,7 +139,7 @@ export function CreatePage({
   const [value, valueHandler] = useState(with_defaults(instanceConfig));
   const config = useConfigContext();
   const zero = Amounts.zeroOfCurrency(config.currency);
-
+  const [settings] = useSettings()
   const inventoryList = Object.values(value.inventoryProducts || {});
   const productList = Object.values(value.products || {});
 
@@ -154,10 +155,10 @@ export function CreatePage({
       order_price: !value.pricing?.order_price
         ? i18n.str`required`
         : !parsedPrice
-        ? i18n.str`not valid`
-        : Amounts.isZero(parsedPrice)
-        ? i18n.str`must be greater than 0`
-        : undefined,
+          ? i18n.str`not valid`
+          : Amounts.isZero(parsedPrice)
+            ? i18n.str`must be greater than 0`
+            : undefined,
     }),
     extra:
       value.extra && !stringIsValidJSON(value.extra)
@@ -167,47 +168,47 @@ export function CreatePage({
       refund_deadline: !value.payments?.refund_deadline
         ? undefined
         : !isFuture(value.payments.refund_deadline)
-        ? i18n.str`should be in the future`
-        : value.payments.pay_deadline &&
-          isBefore(value.payments.refund_deadline, value.payments.pay_deadline)
-        ? i18n.str`refund deadline cannot be before pay deadline`
-        : value.payments.wire_transfer_deadline &&
-          isBefore(
-            value.payments.wire_transfer_deadline,
-            value.payments.refund_deadline,
-          )
-        ? i18n.str`wire transfer deadline cannot be before refund deadline`
-        : undefined,
+          ? i18n.str`should be in the future`
+          : value.payments.pay_deadline &&
+            isBefore(value.payments.refund_deadline, 
value.payments.pay_deadline)
+            ? i18n.str`refund deadline cannot be before pay deadline`
+            : value.payments.wire_transfer_deadline &&
+              isBefore(
+                value.payments.wire_transfer_deadline,
+                value.payments.refund_deadline,
+              )
+              ? i18n.str`wire transfer deadline cannot be before refund 
deadline`
+              : undefined,
       pay_deadline: !value.payments?.pay_deadline
         ? undefined
         : !isFuture(value.payments.pay_deadline)
-        ? i18n.str`should be in the future`
-        : value.payments.wire_transfer_deadline &&
-          isBefore(
-            value.payments.wire_transfer_deadline,
-            value.payments.pay_deadline,
-          )
-        ? i18n.str`wire transfer deadline cannot be before pay deadline`
-        : undefined,
+          ? i18n.str`should be in the future`
+          : value.payments.wire_transfer_deadline &&
+            isBefore(
+              value.payments.wire_transfer_deadline,
+              value.payments.pay_deadline,
+            )
+            ? i18n.str`wire transfer deadline cannot be before pay deadline`
+            : undefined,
       auto_refund_deadline: !value.payments?.auto_refund_deadline
         ? undefined
         : !isFuture(value.payments.auto_refund_deadline)
-        ? i18n.str`should be in the future`
-        : !value.payments?.refund_deadline
-        ? i18n.str`should have a refund deadline`
-        : !isAfter(
-            value.payments.refund_deadline,
-            value.payments.auto_refund_deadline,
-          )
-        ? i18n.str`auto refund cannot be after refund deadline`
-        : undefined,
+          ? i18n.str`should be in the future`
+          : !value.payments?.refund_deadline
+            ? i18n.str`should have a refund deadline`
+            : !isAfter(
+              value.payments.refund_deadline,
+              value.payments.auto_refund_deadline,
+            )
+              ? i18n.str`auto refund cannot be after refund deadline`
+              : undefined,
     }),
     shipping: undefinedIfEmpty({
       delivery_date: !value.shipping?.delivery_date
         ? undefined
         : !isFuture(value.shipping.delivery_date)
-        ? i18n.str`should be in the future`
-        : undefined,
+          ? i18n.str`should be in the future`
+          : undefined,
     }),
   };
   const hasErrors = Object.keys(errors).some(
@@ -227,27 +228,27 @@ export function CreatePage({
         extra: value.extra,
         pay_deadline: value.payments.pay_deadline
           ? {
-              t_s: Math.floor(value.payments.pay_deadline.getTime() / 1000),
-            }
+            t_s: Math.floor(value.payments.pay_deadline.getTime() / 1000),
+          }
           : undefined,
         wire_transfer_deadline: value.payments.wire_transfer_deadline
           ? {
-              t_s: Math.floor(
-                value.payments.wire_transfer_deadline.getTime() / 1000,
-              ),
-            }
+            t_s: Math.floor(
+              value.payments.wire_transfer_deadline.getTime() / 1000,
+            ),
+          }
           : undefined,
         refund_deadline: value.payments.refund_deadline
           ? {
-              t_s: Math.floor(value.payments.refund_deadline.getTime() / 1000),
-            }
+            t_s: Math.floor(value.payments.refund_deadline.getTime() / 1000),
+          }
           : undefined,
         auto_refund: value.payments.auto_refund_deadline
           ? {
-              d_us: Math.floor(
-                value.payments.auto_refund_deadline.getTime() * 1000,
-              ),
-            }
+            d_us: Math.floor(
+              value.payments.auto_refund_deadline.getTime() * 1000,
+            ),
+          }
           : undefined,
         wire_fee_amortization: value.payments.wire_fee_amortization as number,
         max_fee: value.payments.max_fee as string,
@@ -374,13 +375,15 @@ export function CreatePage({
                 inventory={instanceInventory}
               />
 
-              <NonInventoryProductFrom
-                productToEdit={editingProduct}
-                onAddProduct={(p) => {
-                  setEditingProduct(undefined);
-                  return addNewProduct(p);
-                }}
-              />
+              {settings.advanceOrderMode &&
+                <NonInventoryProductFrom
+                  productToEdit={editingProduct}
+                  onAddProduct={(p) => {
+                    setEditingProduct(undefined);
+                    return addNewProduct(p);
+                  }}
+                />
+              }
 
               {allProducts.length > 0 && (
                 <ProductList
@@ -423,8 +426,8 @@ export function CreatePage({
                       discountOrRise > 0 &&
                       (discountOrRise < 1
                         ? `discount of %${Math.round(
-                            (1 - discountOrRise) * 100,
-                          )}`
+                          (1 - discountOrRise) * 100,
+                        )}`
                         : `rise of %${Math.round((discountOrRise - 1) * 100)}`)
                     }
                     tooltip={i18n.str`Amount to be paid by the customer`}
@@ -445,102 +448,108 @@ export function CreatePage({
                 tooltip={i18n.str`Title of the order to be shown to the 
customer`}
               />
 
-              <InputGroup
-                name="shipping"
-                label={i18n.str`Shipping and Fulfillment`}
-                initialActive
-              >
-                <InputDate
-                  name="shipping.delivery_date"
-                  label={i18n.str`Delivery date`}
-                  tooltip={i18n.str`Deadline for physical delivery assured by 
the merchant.`}
-                />
-                {value.shipping?.delivery_date && (
-                  <InputGroup
-                    name="shipping.delivery_location"
-                    label={i18n.str`Location`}
-                    tooltip={i18n.str`address where the products will be 
delivered`}
-                  >
-                    <InputLocation name="shipping.delivery_location" />
-                  </InputGroup>
-                )}
-                <Input
-                  name="shipping.fullfilment_url"
-                  label={i18n.str`Fulfillment URL`}
-                  tooltip={i18n.str`URL to which the user will be redirected 
after successful payment.`}
-                />
-              </InputGroup>
+              {settings.advanceOrderMode &&
+                <InputGroup
+                  name="shipping"
+                  label={i18n.str`Shipping and Fulfillment`}
+                  initialActive
+                >
+                  <InputDate
+                    name="shipping.delivery_date"
+                    label={i18n.str`Delivery date`}
+                    tooltip={i18n.str`Deadline for physical delivery assured 
by the merchant.`}
+                  />
+                  {value.shipping?.delivery_date && (
+                    <InputGroup
+                      name="shipping.delivery_location"
+                      label={i18n.str`Location`}
+                      tooltip={i18n.str`address where the products will be 
delivered`}
+                    >
+                      <InputLocation name="shipping.delivery_location" />
+                    </InputGroup>
+                  )}
+                  <Input
+                    name="shipping.fullfilment_url"
+                    label={i18n.str`Fulfillment URL`}
+                    tooltip={i18n.str`URL to which the user will be redirected 
after successful payment.`}
+                  />
+                </InputGroup>
+              }
 
-              <InputGroup
-                name="payments"
-                label={i18n.str`Taler payment options`}
-                tooltip={i18n.str`Override default Taler payment settings for 
this order`}
-              >
-                <InputDate
-                  name="payments.pay_deadline"
-                  label={i18n.str`Payment deadline`}
-                  tooltip={i18n.str`Deadline for the customer to pay for the 
offer before it expires. Inventory products will be reserved until this 
deadline.`}
-                />
-                <InputDate
-                  name="payments.refund_deadline"
-                  label={i18n.str`Refund deadline`}
-                  tooltip={i18n.str`Time until which the order can be refunded 
by the merchant.`}
-                />
-                <InputDate
-                  name="payments.wire_transfer_deadline"
-                  label={i18n.str`Wire transfer deadline`}
-                  tooltip={i18n.str`Deadline for the exchange to make the wire 
transfer.`}
-                />
-                <InputDate
-                  name="payments.auto_refund_deadline"
-                  label={i18n.str`Auto-refund deadline`}
-                  tooltip={i18n.str`Time until which the wallet will 
automatically check for refunds without user interaction.`}
-                />
+              {settings.advanceOrderMode &&
+                <InputGroup
+                  name="payments"
+                  label={i18n.str`Taler payment options`}
+                  tooltip={i18n.str`Override default Taler payment settings 
for this order`}
+                >
+                  <InputDate
+                    name="payments.pay_deadline"
+                    label={i18n.str`Payment deadline`}
+                    tooltip={i18n.str`Deadline for the customer to pay for the 
offer before it expires. Inventory products will be reserved until this 
deadline.`}
+                  />
+                  <InputDate
+                    name="payments.refund_deadline"
+                    label={i18n.str`Refund deadline`}
+                    tooltip={i18n.str`Time until which the order can be 
refunded by the merchant.`}
+                  />
+                  <InputDate
+                    name="payments.wire_transfer_deadline"
+                    label={i18n.str`Wire transfer deadline`}
+                    tooltip={i18n.str`Deadline for the exchange to make the 
wire transfer.`}
+                  />
+                  <InputDate
+                    name="payments.auto_refund_deadline"
+                    label={i18n.str`Auto-refund deadline`}
+                    tooltip={i18n.str`Time until which the wallet will 
automatically check for refunds without user interaction.`}
+                  />
 
-                <InputCurrency
-                  name="payments.max_fee"
-                  label={i18n.str`Maximum deposit fee`}
-                  tooltip={i18n.str`Maximum deposit fees the merchant is 
willing to cover for this order. Higher deposit fees must be covered in full by 
the consumer.`}
-                />
-                <InputCurrency
-                  name="payments.max_wire_fee"
-                  label={i18n.str`Maximum wire fee`}
-                  tooltip={i18n.str`Maximum aggregate wire fees the merchant 
is willing to cover for this order. Wire fees exceeding this amount are to be 
covered by the customers.`}
-                />
-                <InputNumber
-                  name="payments.wire_fee_amortization"
-                  label={i18n.str`Wire fee amortization`}
-                  tooltip={i18n.str`Factor by which wire fees exceeding the 
above threshold are divided to determine the share of excess wire fees to be 
paid explicitly by the consumer.`}
-                />
-                <InputBoolean
-                  name="payments.createToken"
-                  label={i18n.str`Create token`}
-                  tooltip={i18n.str`Uncheck this option if the merchant 
backend generated an order ID with enough entropy to prevent adversarial 
claims.`}
-                />
-                <InputNumber
-                  name="payments.minimum_age"
-                  label={i18n.str`Minimum age required`}
-                  tooltip={i18n.str`Any value greater than 0 will limit the 
coins able be used to pay this contract. If empty the age restriction will be 
defined by the products`}
-                  help={
-                    minAgeByProducts > 0
-                      ? i18n.str`Min age defined by the producs is 
${minAgeByProducts}`
-                      : undefined
-                  }
-                />
-              </InputGroup>
+                  <InputCurrency
+                    name="payments.max_fee"
+                    label={i18n.str`Maximum deposit fee`}
+                    tooltip={i18n.str`Maximum deposit fees the merchant is 
willing to cover for this order. Higher deposit fees must be covered in full by 
the consumer.`}
+                  />
+                  <InputCurrency
+                    name="payments.max_wire_fee"
+                    label={i18n.str`Maximum wire fee`}
+                    tooltip={i18n.str`Maximum aggregate wire fees the merchant 
is willing to cover for this order. Wire fees exceeding this amount are to be 
covered by the customers.`}
+                  />
+                  <InputNumber
+                    name="payments.wire_fee_amortization"
+                    label={i18n.str`Wire fee amortization`}
+                    tooltip={i18n.str`Factor by which wire fees exceeding the 
above threshold are divided to determine the share of excess wire fees to be 
paid explicitly by the consumer.`}
+                  />
+                  <InputBoolean
+                    name="payments.createToken"
+                    label={i18n.str`Create token`}
+                    tooltip={i18n.str`Uncheck this option if the merchant 
backend generated an order ID with enough entropy to prevent adversarial 
claims.`}
+                  />
+                  <InputNumber
+                    name="payments.minimum_age"
+                    label={i18n.str`Minimum age required`}
+                    tooltip={i18n.str`Any value greater than 0 will limit the 
coins able be used to pay this contract. If empty the age restriction will be 
defined by the products`}
+                    help={
+                      minAgeByProducts > 0
+                        ? i18n.str`Min age defined by the producs is 
${minAgeByProducts}`
+                        : undefined
+                    }
+                  />
+                </InputGroup>
+              }
 
-              <InputGroup
-                name="extra"
-                label={i18n.str`Additional information`}
-                tooltip={i18n.str`Custom information to be included in the 
contract for this order.`}
-              >
-                <Input
+              {settings.advanceOrderMode &&
+                <InputGroup
                   name="extra"
-                  inputType="multiline"
-                  label={`Value`}
-                  tooltip={i18n.str`You must enter a value in JavaScript 
Object Notation (JSON).`}
-                />
-              </InputGroup>
+                  label={i18n.str`Additional information`}
+                  tooltip={i18n.str`Custom information to be included in the 
contract for this order.`}
+                >
+                  <Input
+                    name="extra"
+                    inputType="multiline"
+                    label={`Value`}
+                    tooltip={i18n.str`You must enter a value in JavaScript 
Object Notation (JSON).`}
+                  />
+                </InputGroup>
+              }
             </FormProvider>
 
             <div class="buttons is-right mt-5">
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx
index 8dabfbe12..8965d41c9 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx
@@ -21,7 +21,7 @@
 
 import { AmountJson, Amounts } from "@gnu-taler/taler-util";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { format } from "date-fns";
+import { format, formatDistance } from "date-fns";
 import { Fragment, h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { FormProvider } from "../../../../components/form/FormProvider.js";
@@ -223,6 +223,7 @@ function ClaimedPage({
                     </div>
                   </div>
                 </div>
+
                 <div class="level">
                   <div class="level-left">
                     <div class="level-item">
@@ -419,6 +420,11 @@ function PaidPage({
     }
   }
 
+  const now = new Date()
+  const nextEvent = events.find((e) => {
+    return e.when.getTime() > now.getTime()
+  })
+
   const [value, valueHandler] = useState<Partial<Paid>>(order);
   const { url } = useBackendContext();
   const refundHost = url.replace(/.*:\/\//, ""); // remove protocol part
@@ -504,22 +510,13 @@ function PaidPage({
                           whiteSpace: "nowrap",
                           overflow: "hidden",
                           textOverflow: "ellipsis",
-                          // maxWidth: '100%',
                         }}
                       >
                         <p>
-                          <a
-                            href={order.contract_terms.fulfillment_url}
-                            rel="nofollow"
-                            target="new"
-                          >
-                            {order.contract_terms.fulfillment_url}
-                          </a>
-                        </p>
-                        <p>
-                          {format(
-                            new Date(order.contract_terms.timestamp.t_s * 
1000),
-                            "yyyy/MM/dd HH:mm:ss",
+                          <i18n.Translate>Next event in </i18n.Translate> 
{formatDistance(
+                            nextEvent!.when,
+                            new Date(),
+                            // "yyyy/MM/dd HH:mm:ss",
                           )}
                         </p>
                       </div>
@@ -668,9 +665,9 @@ function UnpaidPage({
                     {order.creation_time.t_s === "never"
                       ? "never"
                       : format(
-                          new Date(order.creation_time.t_s * 1000),
-                          "yyyy-MM-dd HH:mm:ss",
-                        )}
+                        new Date(order.creation_time.t_s * 1000),
+                        "yyyy-MM-dd HH:mm:ss",
+                      )}
                   </p>
                 </div>
               </div>
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx
index d73ba3acc..e68889a92 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx
@@ -67,7 +67,7 @@ export function Timeline({ events: e }: Props) {
                   );
                 case "start":
                   return (
-                    <div class="timeline-marker is-icon is-success">
+                    <div class="timeline-marker is-icon">
                       <i class="mdi mdi-flag " />
                     </div>
                   );
@@ -104,7 +104,7 @@ export function Timeline({ events: e }: Props) {
               }
             })()}
             <div class="timeline-content">
-              <p class="heading">{format(e.when, "yyyy/MM/dd HH:mm:ss")}</p>
+              {e.description !== "now" && <p class="heading">{format(e.when, 
"yyyy/MM/dd HH:mm:ss")}</p>}
               <p>{e.description}</p>
             </div>
           </div>
@@ -117,12 +117,12 @@ export interface Event {
   when: Date;
   description: string;
   type:
-    | "start"
-    | "refund"
-    | "refund-taken"
-    | "wired"
-    | "wired-range"
-    | "deadline"
-    | "delivery"
-    | "now";
+  | "start"
+  | "refund"
+  | "refund-taken"
+  | "wired"
+  | "wired-range"
+  | "deadline"
+  | "delivery"
+  | "now";
 }
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx
index 56d9dda74..37770d273 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx
@@ -164,7 +164,7 @@ export function ListPage({
             <div class="field has-addons">
               {jumpToDate && (
                 <div class="control">
-                  <a class="button" onClick={() => onSelectDate(undefined)}>
+                  <a class="button is-fullwidth" onClick={() => 
onSelectDate(undefined)}>
                     <span
                       class="icon"
                       data-tooltip={i18n.str`clear date filter`}
@@ -191,7 +191,7 @@ export function ListPage({
               <div class="control">
                 <span class="has-tooltip-left" data-tooltip={dateTooltip}>
                   <a
-                    class="button"
+                    class="button  is-fullwidth"
                     onClick={() => {
                       setPickDate(true);
                     }}
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
index 4dde202c4..e20b9bc27 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
@@ -85,34 +85,34 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
     template_contract: !state.template_contract
       ? undefined
       : undefinedIfEmpty({
-          amount: !state.template_contract?.amount
-            ? undefined
-            : !parsedPrice
+        amount: !state.template_contract?.amount
+          ? undefined
+          : !parsedPrice
             ? i18n.str`not valid`
             : Amounts.isZero(parsedPrice)
-            ? i18n.str`must be greater than 0`
-            : undefined,
-          minimum_age:
-            state.template_contract.minimum_age < 0
-              ? i18n.str`should be greater that 0`
+              ? i18n.str`must be greater than 0`
               : undefined,
-          pay_duration: !state.template_contract.pay_duration
-            ? i18n.str`can't be empty`
-            : state.template_contract.pay_duration.d_us === "forever"
+        minimum_age:
+          state.template_contract.minimum_age < 0
+            ? i18n.str`should be greater that 0`
+            : undefined,
+        pay_duration: !state.template_contract.pay_duration
+          ? i18n.str`can't be empty`
+          : state.template_contract.pay_duration.d_us === "forever"
             ? undefined
             : state.template_contract.pay_duration.d_us < 1000 * 1000 //less 
than one second
-            ? i18n.str`to short`
-            : undefined,
-        } as Partial<MerchantTemplateContractDetails>),
+              ? i18n.str`to short`
+              : undefined,
+      } as Partial<MerchantTemplateContractDetails>),
     pos_key: !state.pos_key
       ? !state.pos_algorithm
         ? undefined
         : i18n.str`required`
       : !isBase32RFC3548Charset(state.pos_key)
-      ? i18n.str`just letters and numbers from 2 to 7`
-      : state.pos_key.length !== 32
-      ? i18n.str`size of the key should be 32`
-      : undefined,
+        ? i18n.str`just letters and numbers from 2 to 7`
+        : state.pos_key.length !== 32
+          ? i18n.str`size of the key should be 32`
+          : undefined,
   };
 
   const hasErrors = Object.keys(errors).some(
@@ -139,7 +139,7 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
             >
               <InputWithAddon<Entity>
                 name="template_id"
-                addonBefore={`${backend.url}/instances/templates/`}
+                help={`${backend.url}/instances/templates/${state.template_id 
?? ""}`}
                 label={i18n.str`Identifier`}
                 tooltip={i18n.str`Name of the template in URLs.`}
               />
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx
index 90084f113..0f30efafd 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx
@@ -34,6 +34,7 @@ import { useBackendContext } from 
"../../../../context/backend.js";
 import { useConfigContext } from "../../../../context/config.js";
 import { useInstanceContext } from "../../../../context/instance.js";
 import { MerchantBackend } from "../../../../declaration.js";
+import { stringifyPayTemplateUri } from "@gnu-taler/taler-util";
 
 type Entity = MerchantBackend.Template.UsingTemplateDetails;
 
@@ -64,46 +65,47 @@ export function QrPage({ template, id: templateId, onBack 
}: Props): VNode {
   const fixedAmount = !!template.template_contract.amount;
   const fixedSummary = !!template.template_contract.summary;
 
-  const params = new URLSearchParams();
+  const templateParams: Record<string, string> = {}
   if (!fixedAmount) {
     if (state.amount) {
-      params.append("amount", state.amount);
+      templateParams.amount = state.amount
     } else {
-      params.append("amount", config.currency);
+      templateParams.amount = config.currency
     }
   }
+
   if (!fixedSummary) {
-    params.append("summary", state.summary ?? "");
+    templateParams.summary = state.summary ?? ""
   }
 
-  const paramsStr = fixedAmount && fixedSummary ? "" : "?" + params.toString();
-  const merchantURL = new URL(backendUrl);
-
-  const talerProto =
-    merchantURL.protocol === "http:" ? "taler+http:" : "taler:";
+  const merchantBaseUrl = new URL(backendUrl).href;
 
-  const payTemplateUri = 
`${talerProto}//pay-template/${merchantURL.hostname}/${templateId}${paramsStr}`;
+  const payTemplateUri = stringifyPayTemplateUri({
+    merchantBaseUrl,
+    templateId,
+    templateParams
+  })
 
   const issuer = encodeURIComponent(
-    `${new URL(backendUrl).hostname}/${instanceId}`,
+    `${new URL(backendUrl).host}/${instanceId}`,
   );
   const oauthUri = !template.pos_algorithm
     ? undefined
     : template.pos_algorithm === 1
-    ? 
`otpauth://totp/${issuer}:${templateId}?secret=${template.pos_key}&issuer=${issuer}&algorithm=SHA1&digits=8&period=30`
-    : template.pos_algorithm === 2
-    ? 
`otpauth://totp/${issuer}:${templateId}?secret=${template.pos_key}&issuer=${issuer}&algorithm=SHA1&digits=8&period=30`
-    : undefined;
+      ? 
`otpauth://totp/${issuer}:${templateId}?secret=${template.pos_key}&issuer=${issuer}&algorithm=SHA1&digits=8&period=30`
+      : template.pos_algorithm === 2
+        ? 
`otpauth://totp/${issuer}:${templateId}?secret=${template.pos_key}&issuer=${issuer}&algorithm=SHA1&digits=8&period=30`
+        : undefined;
 
   const keySlice = template.pos_key?.substring(0, 4);
 
   const oauthUriWithoutSecret = !template.pos_algorithm
     ? undefined
     : template.pos_algorithm === 1
-    ? 
`otpauth://totp/${issuer}:${templateId}?secret=${keySlice}...&issuer=${issuer}&algorithm=SHA1&digits=8&period=30`
-    : template.pos_algorithm === 2
-    ? 
`otpauth://totp/${issuer}:${templateId}?secret=${keySlice}...&issuer=${issuer}&algorithm=SHA1&digits=8&period=30`
-    : undefined;
+      ? 
`otpauth://totp/${issuer}:${templateId}?secret=${keySlice}...&issuer=${issuer}&algorithm=SHA1&digits=8&period=30`
+      : template.pos_algorithm === 2
+        ? 
`otpauth://totp/${issuer}:${templateId}?secret=${keySlice}...&issuer=${issuer}&algorithm=SHA1&digits=8&period=30`
+        : undefined;
   return (
     <div>
       {oauthUri && (
diff --git a/packages/merchant-backoffice-ui/src/paths/notfound/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/notfound/index.tsx
index b58948dbd..061a67025 100644
--- a/packages/merchant-backoffice-ui/src/paths/notfound/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/notfound/index.tsx
@@ -25,7 +25,6 @@ import { Link } from "preact-router";
 export default function NotFoundPage(): VNode {
   return (
     <div>
-      <h1>Error 404</h1>
       <p>That page doesn&apos;t exist.</p>
       <Link href="/">
         <h4>Back to Home</h4>
diff --git a/packages/merchant-backoffice-ui/src/paths/settings/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/settings/index.tsx
new file mode 100644
index 000000000..128450553
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/paths/settings/index.tsx
@@ -0,0 +1,77 @@
+import { VNode, h } from "preact";
+import { LangSelector } from "../../components/menu/LangSelector.js";
+import { useLang, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { InputToggle } from "../../components/form/InputToggle.js";
+import { Settings, useSettings } from "../../hooks/useSettings.js";
+import { FormErrors, FormProvider } from 
"../../components/form/FormProvider.js";
+import { useState } from "preact/hooks";
+
+function getBrowserLang(): string | undefined {
+  if (typeof window === "undefined") return undefined;
+  if (window.navigator.languages) return window.navigator.languages[0];
+  if (window.navigator.language) return window.navigator.language;
+  return undefined;
+}
+
+export function Settings(): VNode {
+  const { i18n } = useTranslationContext()
+  const borwserLang = getBrowserLang()
+  const { update } = useLang()
+
+  const [value, updateValue] = useSettings()
+  const errors: FormErrors<Settings> = {
+  }
+
+  function valueHandler(s: (d: Partial<Settings>) => Partial<Settings>): void {
+    const next = s(value)
+    updateValue("advanceOrderMode", next.advanceOrderMode ?? false)
+  }
+
+  return <div>
+    <section class="section is-main-section">
+      <div class="columns">
+        <div class="column" />
+        <div class="column is-four-fifths">
+          <div class="field is-horizontal">
+            <div class="field-label is-normal">
+              <label class="label" style={{ width: 200 }}>
+                <i18n.Translate>Language</i18n.Translate>
+                <span class="icon has-tooltip-right" data-tooltip={"Force 
language setting instance of taking the browser"}>
+                  <i class="mdi mdi-information" />
+                </span>
+              </label>
+            </div>
+            <div class="field has-addons">
+              <LangSelector />
+              &nbsp;
+              {borwserLang !== undefined && <button
+                data-tooltip={i18n.str`generate random secret key`}
+                class="button is-info mr-3"
+                onClick={(e) => {
+                  update(borwserLang.substring(0, 2))
+                }}
+              >
+                <i18n.Translate>Set default</i18n.Translate>
+              </button>}
+            </div>
+          </div>
+          <FormProvider<Settings>
+            name="settings"
+            errors={errors}
+            object={value}
+            valueHandler={valueHandler}
+          >
+            <InputToggle<Settings>
+              label={i18n.str`Advance order creation`}
+              tooltip={i18n.str`Shows more options in the order creation form`}
+              name="advanceOrderMode"
+            />
+          </FormProvider>
+
+
+        </div>
+        <div class="column" />
+      </div>
+    </section>
+  </div>
+}
\ No newline at end of file
diff --git a/packages/merchant-backoffice-ui/src/scss/main.scss 
b/packages/merchant-backoffice-ui/src/scss/main.scss
index ad698eb26..c4be8aa73 100644
--- a/packages/merchant-backoffice-ui/src/scss/main.scss
+++ b/packages/merchant-backoffice-ui/src/scss/main.scss
@@ -52,6 +52,8 @@ $tooltip-color: red;
 @import 
"../../node_modules/@creativebulma/bulma-tooltip/dist/bulma-tooltip.min.css";
 @import "../../node_modules/bulma-timeline/dist/css/bulma-timeline.min.css";
 
+@import "toggle";
+
 .notification {
   background-color: transparent;
 }
@@ -82,7 +84,7 @@ $tooltip-color: red;
   pointer-events: none;
 }
 
-.toast > .message {
+.toast>.message {
   white-space: pre-wrap;
   opacity: 80%;
 }
@@ -92,6 +94,7 @@ div {
     position: relative;
     pointer-events: none;
     opacity: 0.5;
+
     &:after {
       // @include loader;
       position: absolute;
@@ -104,7 +107,7 @@ div {
   }
 }
 
-input[type="checkbox"]:indeterminate + .check {
+input[type="checkbox"]:indeterminate+.check {
   background: red !important;
 }
 
@@ -125,6 +128,7 @@ input[type="checkbox"]:indeterminate + .check {
 tr:hover .right-sticky {
   background-color: hsl(0, 0%, 80%);
 }
+
 .table.is-striped tbody tr:nth-child(even):hover .right-sticky {
   background-color: hsl(0, 0%, 95%);
 }
@@ -181,11 +185,11 @@ div[data-tooltip]::before {
   position: absolute;
 }
 
-.modal-card-body > p {
+.modal-card-body>p {
   padding: 1em;
 }
 
-.modal-card-body > p.warning {
+.modal-card-body>p.warning {
   background-color: #fffbdd;
   border: solid 1px #f2e9bf;
-}
+}
\ No newline at end of file
diff --git a/packages/demobank-ui/src/scss/toggle.scss 
b/packages/merchant-backoffice-ui/src/scss/toggle.scss
similarity index 100%
copy from packages/demobank-ui/src/scss/toggle.scss
copy to packages/merchant-backoffice-ui/src/scss/toggle.scss
diff --git a/packages/taler-util/src/taleruri.ts 
b/packages/taler-util/src/taleruri.ts
index 777cb5245..fff1ca833 100644
--- a/packages/taler-util/src/taleruri.ts
+++ b/packages/taler-util/src/taleruri.ts
@@ -767,7 +767,7 @@ function getUrlInfo(
   const qp = new URLSearchParams();
   let withParams = false;
   Object.entries(params).forEach(([name, value]) => {
-    if (value) {
+    if (value !== undefined) {
       withParams = true;
       qp.append(name, value);
     }

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