gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant-backoffice] branch master updated (755a081 -> 8e91c94)


From: gnunet
Subject: [taler-merchant-backoffice] branch master updated (755a081 -> 8e91c94)
Date: Thu, 27 May 2021 16:02:45 +0200

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

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

    from 755a081  fix typo: s/doest not match with/does not match/
     new 1409f59  removing message-po and testing on storybook
     new 8e91c94  some fixes

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


Summary of changes:
 CHANGELOG.md                                       | 136 +++---------
 packages/frontend/.storybook/main.js               |  24 +--
 packages/frontend/.storybook/preview.js            |   1 +
 packages/frontend/preact.config.js                 |  32 +--
 packages/frontend/src/InstanceRoutes.tsx           |  17 +-
 .../frontend/src/components/exception/loading.tsx  |  10 +-
 .../frontend/src/components/exception/login.tsx    |  11 +-
 .../frontend/src/components/form/InputSecured.tsx  |  74 +++++--
 packages/frontend/src/components/menu/index.tsx    |  10 +-
 packages/frontend/src/components/modal/index.tsx   |  14 +-
 .../notifications/CreatedSuccessfully.tsx          |   4 +-
 .../src/components/product/ProductForm.tsx         |   2 +-
 packages/frontend/src/context/backend.ts           |   6 +-
 packages/frontend/src/context/instance.ts          |   1 +
 packages/frontend/src/declaration.d.ts             |  45 ++++
 packages/frontend/src/hooks/instance.ts            |  10 +-
 packages/frontend/src/i18n/index.tsx               |   6 +-
 .../frontend/src/paths/admin/create/CreatePage.tsx |   3 +-
 .../paths/instance/orders/create/CreatePage.tsx    |  66 +++---
 .../src/paths/instance/orders/create/index.tsx     |   5 -
 .../src/paths/instance/products/list/Table.tsx     |   2 +-
 .../paths/instance/reserves/create/CreatePage.tsx  | 128 ++++++++++--
 .../reserves/create/CreatedSuccessfully.tsx        |  39 +++-
 .../src/paths/instance/reserves/create/index.tsx   |   8 +-
 .../paths/instance/reserves/details/DetailPage.tsx |  22 +-
 .../instance/reserves/list/AutorizeTipModal.tsx    |  23 ++-
 .../src/paths/instance/reserves/list/Table.tsx     | 227 +++++++++------------
 .../src/paths/instance/transfers/list/index.tsx    |  37 ++--
 .../src/paths/instance/update/UpdatePage.tsx       |  90 ++++++--
 .../frontend/src/paths/instance/update/index.tsx   |  15 +-
 packages/frontend/src/utils/amount.ts              |  41 ++--
 packages/frontend/src/utils/constants.ts           |   1 +
 32 files changed, 643 insertions(+), 467 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 45bfb40..ad3634d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,8 +17,6 @@ and this project adheres to [Semantic 
Versioning](https://semver.org/spec/v2.0.0
  - prune scss styles to reduce size
  - fix mobile: some things are still on the left
  - edit button to go to instance settings
- - check if there is a way to remove auto async for /routes 
/components/{async,routes} so it can be turned on when building 
non-single-bundle
- - navigation to another instance should not do full refresh
  - cleanup instance and token management, because code is a mess and can be 
refactored 
 
  - unlock a product when is locked
@@ -26,132 +24,44 @@ and this project adheres to [Semantic 
Versioning](https://semver.org/spec/v2.0.0
  - translation missing: yup (check for some ot  her dynamic message)
  - contract terms
  - fulfillment url should check absolute url or relative to the merchant domain
- - duplicate order button
- - simplify order 
- - react routing refactor to use query parameters from history
  - create taler ui
  - contract terms in the wallet
- - when backoffice get a response of the merchant that have info about the 
reponsse of the exchange, the error is not readed correctly... see wire transfer
+ - when backoffice get a response of the merchant that have info about the 
reponse of the exchange, the error is not readed correctly... see wire transfer
  - when creating the first default instance, the page keeps reloading 
preventing for filling the form
  - 
- - payto auto remove (add if is missing)
- - back cancel in all dialog (order)
- - transfer use the tabs instead of three state
- - spining while working, 
- - disabled button grey out
- - add timeout to the request
- - hash
 
- - delete button, without 
- - delete transaction if verified is missing
+ - api key remove from login, and bearer
+ - 'secret-token:'  add missing, remove when the user set
+ - change the password and stay there logged in
+ - auth token section
+ - header with the instance id
+ - create a section with the auth token
+ - bug after clicking change, cycliing over change the password  
+ - reserve created suffcefluy
+ - message => wire transfer subject
+ - reservers
+ - exchange https:// pattern (wallet util base url helper)
+ - valid wire method
 
- - update instance
- - auth token: status => external/managed
+ - create instance with auth token
+ - add more information about reserve when is not founded
+ - replace manipulation of amount with taler util libraries
 
+ - check, not running the backend, load the SPA, then start running the 
backend. after this click the login button should enter the app but doesnt do 
anything
 
- - price not required: product page has required field that should not be 
required
- - animation on load
+
+wallet
+
+ - show transaction with error state
+ - add developer mode in settings, this will show debug tab
+ - add transaction details, and delete button
 
 ## [Unreleased]
- - fixed bug when updating token and not admin
- - showing a yellow bar on non-default instance navigation (admin)
-
-## [0.0.6] - 2021-03-25
- - complete order list information (#6793)
- - complete product list information (#6792)
- - missing fields in the instance update
- - https://bugs.gnunet.org/view.php?id=6815
- 
-
-## [0.0.5] - 2021-03-18
- - change the admin title to "instances" if we are listing the instances and 
"settings: $ID" on updating instances (#6790)
- - update title with: Taler Backoffice: $PAGE_TITLE (#6790)
- - paths should be /orders instead of /o (same others)
- - if there is enough space for tables in mobile, make the scrollables (#6789)
- - show create default instance if it is not already
- - notifications should tale place between title and content, and not disapear 
(#6788)
- - create a loading page to be use when the data is not ready (test at 
#/loading)
- - confirmation page when creating instances
-
-## [0.0.4] - 2021-03-11
- - prevent letters to be input in numbers
- - instance id in instance list should be clickable
- - edit button to go to instance settings
- - add order section
- - add product section
- - add tips section
- - add transfers section
- - initial state before login
- - logout takes you to a initial state, not showing error messages
- - change the admin title to "instances" if we are listing the instances and 
"settings: $ID" on updating instances
- - update title with: Taler Backoffice: $PAGE_TITLE
-
-## [0.0.3] - 2021-03-04
- - submit form on key press == enter
- - version of backoffice in sidebar
- - fixed login dialog on mobile
- - LangSelector ascomponent
- - refactored Navigation and Sidebar
- - do not display Logout when there is no token
- - fix: Login Page should show on unauthorized
- - fix: row clicking on card table was overriding checkbox onClick
- - remove headers of the page
- - clear all tokens now remove backend-url
- - remove checkbox from auth token, use button (manage auth)
- - auth token config as popup with 3 actions (clear (sure?), cancel, set token)
- - new password enpoint
- - bug: there is missing a mutate call when updating to remove the instance 
from cache
- 
-
-## [0.0.2] - 2021-02-25
- - REFACTOR: remove react-i18n and implement messageformat
- - REFACTOR: routes definitions to allow nested routes and tokens
- - REFACTOR: remove yup from input form defitions
- - added PORT environment variable for `make dev` and `make serve` 
- - added `make dist` and `make install`
- - remove last '/' on the backend url 
- - what happend if cannot access the config
- - reorder the fields from the address/juriction section (take example)
- - save every auth token of different instances
- - remove footer
- - show the connection state (url, currency, version) in the sidebar
- - add backend url without slash
- - added linter rule for source header
- - bug: set text int the intpu date (seconds)
- - row in the list instance are now clickable
- - re implemented the language selector, remove the current lang from the 
dropdown
- - remove payment adress and public key from instance listing
- - fix bug on CORS error
- - moved the login button to the sidebar
- - remove the details page, go directly to the update page
- - login modal: url before token, and removed the checkbox
- - added payto:// to the field
- - validate payto_uris on add
 
 ## [0.0.1] - 2021-02-18
 ### Changed
- - button of the form to the right
- - add supported currency for Amount fields (like taler bank)
- - rename name to business name
- - change auth field to have a checkbox that activate the validation and show 
an input to set the token
- - rename PayTo URI to bank account
- - change id input to reflect that is going to be use in the url (prepend the 
backend url as a non editable and put the input after)
- - refactor: change create popup to create page
- - add the information popup into the fields to help with the description 
(like https://b2b.dab-bank.de/smartbroker/)
- - take default lang from the browser for localization  
- - refactor update page
- - Login button should be centered
- - replace default exports for named exports
 
 ### Added
- - implement taler built system (bootstrap, configure, makefile)
- - implement pnpm
- - take the url where the spa was loaded as a default backend url 
- - let the user change the backend url when ask for the auth token
- - take the currency from merchant-backend
- - change the input PayTO URI to a string field with a + button to add more
- - format duration as human readable
- - add copyright headers to every source file
 
 ### Deprecated
 
diff --git a/packages/frontend/.storybook/main.js 
b/packages/frontend/.storybook/main.js
index 809b0b7..39d042b 100644
--- a/packages/frontend/.storybook/main.js
+++ b/packages/frontend/.storybook/main.js
@@ -35,18 +35,18 @@ module.exports = {
     // You can change the configuration based on that.
     // 'PRODUCTION' is used when building the static version of storybook.
     // Make whatever fine-grained changes you need
-    config.module.rules.push({
-      test: [/\.pot?$/, /\.mo$/],
-      loader: require.resolve('messageformat-po-loader'),
-      options: {
-        biDiSupport: false,
-        defaultCharset: null,
-        defaultLocale: 'en',
-        forceContext: false,
-        pluralFunction: null,
-        verbose: false
-      }
-    });
+    // config.module.rules.push({
+    //   test: [/\.pot?$/, /\.mo$/],
+    //   loader: require.resolve('messageformat-po-loader'),
+    //   options: {
+    //     biDiSupport: false,
+    //     defaultCharset: null,
+    //     defaultLocale: 'en',
+    //     forceContext: false,
+    //     pluralFunction: null,
+    //     verbose: false
+    //   }
+    // });
     // Return the altered config
     return config;
   },
diff --git a/packages/frontend/.storybook/preview.js 
b/packages/frontend/.storybook/preview.js
index a483dcd..f511267 100644
--- a/packages/frontend/.storybook/preview.js
+++ b/packages/frontend/.storybook/preview.js
@@ -17,6 +17,7 @@
 import "../src/scss/main.scss"
 import { ConfigContextProvider } from '../src/context/config'
 import { TranslationProvider } from '../src/context/translation'
+import { h } from 'preact';
 
 const mockConfig = {
   backendURL: 'http://demo.taler.net',
diff --git a/packages/frontend/preact.config.js 
b/packages/frontend/preact.config.js
index 31a46e7..dd23ac5 100644
--- a/packages/frontend/preact.config.js
+++ b/packages/frontend/preact.config.js
@@ -39,23 +39,23 @@ export default {
     );
 
     // allow import for gettext files
-    config.resolve.extensions.push('.po');
+    // config.resolve.extensions.push('.po');
     // transform .po format into messageformat
-    config.module.rules.push({
-      enforce: 'pre',
-      test: /\.po$/,
-      use: [{
-        loader: 'messageformat-po-loader',
-        options: {
-          biDiSupport: false,
-          defaultCharset: null,
-          defaultLocale: 'en',
-          forceContext: false,
-          pluralFunction: null,
-          verbose: false
-        }
-      }],
-    });
+    // config.module.rules.push({
+    //   enforce: 'pre',
+    //   test: /\.po$/,
+    //   use: [{
+    //     loader: 'messageformat-po-loader',
+    //     options: {
+    //       biDiSupport: false,
+    //       defaultCharset: null,
+    //       defaultLocale: 'en',
+    //       forceContext: false,
+    //       pluralFunction: null,
+    //       verbose: false
+    //     }
+    //   }],
+    // });
 
     // config.plugins.push(new I18n())
   }
diff --git a/packages/frontend/src/InstanceRoutes.tsx 
b/packages/frontend/src/InstanceRoutes.tsx
index 402c760..a7e4227 100644
--- a/packages/frontend/src/InstanceRoutes.tsx
+++ b/packages/frontend/src/InstanceRoutes.tsx
@@ -95,17 +95,20 @@ export function InstanceRoutes({ id, admin }: Props): VNode 
{
     addTokenCleaner(cleaner);
   }, [addTokenCleaner, cleaner]);
 
-  const updateLoginStatus = (url: string, token?: string) => {
-    changeBackend(url);
-    if (!token) return
+  const changeToken = (token?:string) => {
     if (admin) {
       updateToken(token);
     } else {
       updateDefaultToken(token)
     }
+  }
+  const updateLoginStatus = (url: string, token?: string) => {
+    changeBackend(url);
+    if (!token) return
+    changeToken(token)
   };
 
-  const value = useMemo(() => ({ id, token, admin }), [id, token, admin])
+  const value = useMemo(() => ({ id, token, admin, changeToken }), [id, token, 
admin])
 
   const ServerErrorRedirectTo = (to: InstancePaths | AdminPaths) => (error: 
HttpError) => {
     setGlobalNotification({
@@ -297,14 +300,14 @@ export function Redirect({ to }: { to: string }): null {
 }
 
 function AdminInstanceUpdatePage({ id, ...rest }: { id: string } & 
InstanceUpdatePageProps) {
-  const [token, updateToken] = useBackendInstanceToken(id);
-  const value = useMemo(() => ({ id, token, admin: true }), [id, token])
+  const [token, changeToken] = useBackendInstanceToken(id);
   const { changeBackend } = useBackendContext();
   const updateLoginStatus = (url: string, token?: string) => {
     changeBackend(url);
     if (token)
-      updateToken(token);
+    changeToken(token);
   };
+  const value = useMemo(() => ({ id, token, admin: true, changeToken }), [id, 
token])
   const i18n = useTranslator();
   return <InstanceContextProvider value={value}>
     <InstanceUpdatePage {...rest}
diff --git a/packages/frontend/src/components/exception/loading.tsx 
b/packages/frontend/src/components/exception/loading.tsx
index 40c7c7b..f2139a1 100644
--- a/packages/frontend/src/components/exception/loading.tsx
+++ b/packages/frontend/src/components/exception/loading.tsx
@@ -23,10 +23,10 @@ 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%' }}>
-    <div class="column is-one-fifth">
-      <div class="lds-ring">
-        <div /><div /><div /><div />
-      </div>
-    </div>
+    <Spinner />
   </div>
+}
+
+export function Spinner(): VNode {
+  return <div class="lds-ring"><div /><div /><div /><div /></div>
 }
\ No newline at end of file
diff --git a/packages/frontend/src/components/exception/login.tsx 
b/packages/frontend/src/components/exception/login.tsx
index 1132bfe..611af1a 100644
--- a/packages/frontend/src/components/exception/login.tsx
+++ b/packages/frontend/src/components/exception/login.tsx
@@ -36,6 +36,13 @@ export function LoginModal({ onConfirm, withMessage }: 
Props): VNode {
   const { admin, token: instanceToken } = useInstanceContext()
   const [token, setToken] = useState(!admin ? baseToken : instanceToken || '')
   
+  function updateToken(token:string) {
+    const value = token && token.startsWith('secret-token:')?
+      token.substring('secret-token:'.length) : token
+
+    setToken(`secret-token:${value}`)
+  }
+
   const [url, setURL] = useState(backendUrl)
   const i18n = useTranslator()
 
@@ -46,7 +53,7 @@ export function LoginModal({ onConfirm, withMessage }: 
Props): VNode {
           <p class="modal-card-title">{i18n`Login required`}</p>
         </header>
         <section class="modal-card-body" style={{ border: '1px solid', 
borderTop: 0, borderBottom: 0 }}>
-          {i18n`Please enter your auth token. Token should have 
"secret-token:" and start with Bearer or ApiKey`}
+          {i18n`Please enter your auth token.`}
           <div class="field is-horizontal">
             <div class="field-label is-normal">
               <label class="label">URL</label>
@@ -73,7 +80,7 @@ export function LoginModal({ onConfirm, withMessage }: 
Props): VNode {
                   <input class="input" type="text" placeholder={"set new 
token"} name="token"
                     onKeyPress={e => e.keyCode === 13 ? onConfirm(url, token ? 
token : undefined) : null}
                     value={token} 
-                    onInput={(e): void => setToken(e?.currentTarget.value)}
+                    onInput={(e): void => updateToken(e?.currentTarget.value)}
                   />
                 </p>
               </div>
diff --git a/packages/frontend/src/components/form/InputSecured.tsx 
b/packages/frontend/src/components/form/InputSecured.tsx
index 6e3059b..64737e3 100644
--- a/packages/frontend/src/components/form/InputSecured.tsx
+++ b/packages/frontend/src/components/form/InputSecured.tsx
@@ -21,7 +21,6 @@
 import { Fragment, h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { Translate } from "../../i18n";
-import { UpdateTokenModal } from "../modal";
 import { InputProps, useField } from "./useField";
 
 export type Props<T> = InputProps<T>;
@@ -40,7 +39,7 @@ export function InputSecured<T>({ name, readonly, 
placeholder, tooltip, label, h
   const { error, value, initial, onChange, toStr, fromStr } = 
useField<T>(name);
 
   const [active, setActive] = useState(false);
-  const [newValue, setNuewValue] = useState("");
+  const [newValue, setNuewValue] = useState("")
 
   return <Fragment>
     <div class="field is-horizontal">
@@ -53,22 +52,65 @@ export function InputSecured<T>({ name, readonly, 
placeholder, tooltip, label, h
         </label>
       </div>
       <div class="field-body is-flex-grow-3">
-        <div class="field has-addons">
-          <button class="button" onClick={(): void => { setActive(!active); }} 
>
-            <div class="icon is-left"><i class="mdi mdi-lock-reset" /></div>
-            <span><Translate>Manage token</Translate></span>
-          </button>
-          <TokenStatus prev={initial} post={value} />
-        </div>
+        {!active ?
+          <Fragment>
+            <div class="field has-addons">
+              <button class="button" onClick={(): void => { 
setActive(!active); }} >
+                <div class="icon is-left"><i class="mdi mdi-lock-reset" 
/></div>
+                <span><Translate>Manage token</Translate></span>
+              </button>
+              <TokenStatus prev={initial} post={value} />
+            </div>
+          </Fragment> :
+          <Fragment>
+            <div class="field has-addons">
+              <div class="control">
+                <a class="button is-static">secret-token:</a>
+              </div>
+              <div class="control is-expanded">
+                <input class="input" type="text"
+                  placeholder={placeholder} readonly={readonly || !active}
+                  disabled={readonly || !active}
+                  name={String(name)} value={newValue}
+                  onInput={(e): void => {
+                    setNuewValue(e.currentTarget.value)
+                  }} />
+                {help}
+              </div>
+              <div class="control">
+                <button class="button is-info" disabled={fromStr(newValue) === 
value} onClick={(): void => { onChange(fromStr(newValue)); setActive(!active); 
setNuewValue(""); }} >
+                  <div class="icon is-left"><i class="mdi mdi-lock-outline" 
/></div>
+                  <span><Translate>Update</Translate></span>
+                </button>
+              </div>
+            </div>
+          </Fragment>
+        }
         {error ? <p class="help is-danger">{error}</p> : null}
       </div>
     </div>
-    {active && <UpdateTokenModal oldToken={initial}
-      onCancel={() => { onChange(initial!); setActive(false); }}
-      onClear={() => { onChange(null!); setActive(false); }}
-      onConfirm={(newToken) => { 
-        onChange(newToken as any); setActive(false)
-       }}
-    />}
+    {active &&
+      <div class="field is-horizontal">
+        <div class="field-body is-flex-grow-3">
+          <div class="level" style={{ width: '100%' }}>
+            <div class="level-right is-flex-grow-1">
+              <div class="level-item">
+                <button class="button is-danger" disabled={null === value || 
undefined === value} onClick={(): void => { onChange(null!); 
setActive(!active); setNuewValue(""); }} >
+                  <div class="icon is-left"><i class="mdi 
mdi-lock-open-variant" /></div>
+                  <span><Translate>Remove</Translate></span>
+                </button>
+              </div>
+              <div class="level-item">
+                <button class="button " onClick={(): void => { 
onChange(initial!); setActive(!active); setNuewValue(""); }} >
+                  <div class="icon is-left"><i class="mdi 
mdi-lock-open-variant" /></div>
+                  <span><Translate>Cancel</Translate></span>
+                </button>
+              </div>
+            </div>
+
+          </div>
+        </div>
+      </div>
+    }
   </Fragment >;
 }
diff --git a/packages/frontend/src/components/menu/index.tsx 
b/packages/frontend/src/components/menu/index.tsx
index 5140eb0..31826ed 100644
--- a/packages/frontend/src/components/menu/index.tsx
+++ b/packages/frontend/src/components/menu/index.tsx
@@ -30,16 +30,16 @@ function getInstanceTitle(path: string, id: string): string 
{
     // case InstancePaths.details: return `${id}`
     case InstancePaths.update: return `${id}: Settings`
     case InstancePaths.order_list: return `${id}: Orders`
-    // case InstancePaths.order_new: return `${id}: New order`
+    case InstancePaths.order_new: return `${id}: New order`
     case InstancePaths.order_details: return `${id}: Detail of the order`
     case InstancePaths.product_list: return `${id}: Products`
     case InstancePaths.product_new: return `${id}: New product`
     case InstancePaths.product_update: return `${id}: Update product`
-    // case InstancePaths.tips_list: return `${id}: Tips`
-    // case InstancePaths.tips_new: return `${id}: New tip`
-    // case InstancePaths.tips_update: return `${id}: Update tip`
+    case InstancePaths.reserves_details: return `${id}: Detail of a reserve`
+    case InstancePaths.reserves_new: return `${id}: New reserve`
+    case InstancePaths.reserves_list: return `${id}: Reserves`
     case InstancePaths.transfers_list: return `${id}: Transfers`
-    // case InstancePaths.transfers_new: return `${id}: New Transfer`
+    case InstancePaths.transfers_new: return `${id}: New transfer`
     default: return '';
   }
 }
diff --git a/packages/frontend/src/components/modal/index.tsx 
b/packages/frontend/src/components/modal/index.tsx
index 747019c..cba1ce8 100644
--- a/packages/frontend/src/components/modal/index.tsx
+++ b/packages/frontend/src/components/modal/index.tsx
@@ -25,6 +25,7 @@ import { useState } from "preact/hooks";
 import { useInstanceContext } from "../../context/instance";
 import { Translate, useTranslator } from "../../i18n";
 import { DEFAULT_REQUEST_TIMEOUT } from "../../utils/constants";
+import { Loading, Spinner } from "../exception/loading";
 import { FormProvider } from "../form/FormProvider";
 import { Input } from "../form/Input";
 
@@ -167,20 +168,23 @@ export function UpdateTokenModal({ onCancel, onClear, 
onConfirm, oldToken }: Upd
 
 export function LoadingModal({ onCancel }: { onCancel: () => void }): VNode {
   const i18n = useTranslator()
-  return <div class={"modal is-active"}>
+  return <div class="modal is-active">
     <div class="modal-background " onClick={onCancel} />
     <div class="modal-card">
       <header class="modal-card-head">
-        <p class="modal-card-title"><Translate>Operation is taking to much 
time</Translate></p>
+        <p class="modal-card-title"><Translate>Operation in 
progress...</Translate></p>
       </header>
       <section class="modal-card-body">
-        <p><Translate>You can wait a little longer or abort the request to the 
backend. If the problem persist
-        contact the administrator.</Translate></p>
+        <div class="columns">
+          <div class="column" />
+          <Spinner />
+          <div class="column" />
+        </div>
         <p>{i18n`The operation will be automatically canceled after 
${DEFAULT_REQUEST_TIMEOUT} seconds`}</p>
       </section>
       <footer class="modal-card-foot">
         <div class="buttons is-right" style={{ width: '100%' }}>
-          <button class="button " onClick={onCancel} 
><Translate>Abort</Translate></button>
+          <button class="button " onClick={onCancel} 
><Translate>Cancel</Translate></button>
         </div>
       </footer>
     </div>
diff --git 
a/packages/frontend/src/components/notifications/CreatedSuccessfully.tsx 
b/packages/frontend/src/components/notifications/CreatedSuccessfully.tsx
index 8e2eee2..5f10e4b 100644
--- a/packages/frontend/src/components/notifications/CreatedSuccessfully.tsx
+++ b/packages/frontend/src/components/notifications/CreatedSuccessfully.tsx
@@ -26,9 +26,9 @@ interface Props {
 }
 
 export function CreatedSuccessfully({ children, onConfirm, onCreateAnother }: 
Props): VNode {
-  return <div class="columns is-fullwidth is-vcentered content-full-size">
+  return <div class="columns is-fullwidth is-vcentered mt-3">
     <div class="column" />
-    <div class="column is-three-quarters">
+    <div class="column is-four-fifths">
       <div class="card">
         <header class="card-header has-background-success">
           <p class="card-header-title has-text-white-ter">
diff --git a/packages/frontend/src/components/product/ProductForm.tsx 
b/packages/frontend/src/components/product/ProductForm.tsx
index 2729247..f6d52a7 100644
--- a/packages/frontend/src/components/product/ProductForm.tsx
+++ b/packages/frontend/src/components/product/ProductForm.tsx
@@ -95,7 +95,7 @@ export function ProductForm({ onSubscribe, initial, 
alreadyExist, }: Props) {
   return <div>
     <FormProvider<Entity> name="product" errors={errors} object={value} 
valueHandler={valueHandler} >
 
-      {alreadyExist ? undefined : <InputWithAddon<Entity> name="product_id" 
addonBefore={`${backend.url}/product/`} label={i18n`ID`} tooltip={i18n`unique 
name identification`} />}
+      {alreadyExist ? undefined : <InputWithAddon<Entity> name="product_id" 
addonBefore={`${backend.url}/product/`} label={i18n`ID`} tooltip={i18n`display 
name identification`} />}
 
       <InputImage<Entity> name="image" label={i18n`Image`} tooltip={i18n`photo 
of the product`} />
       <Input<Entity> name="description" inputType="multiline" 
label={i18n`Description`} tooltip={i18n`full-length description`} />
diff --git a/packages/frontend/src/context/backend.ts 
b/packages/frontend/src/context/backend.ts
index e33cdd5..c1b2c14 100644
--- a/packages/frontend/src/context/backend.ts
+++ b/packages/frontend/src/context/backend.ts
@@ -47,7 +47,11 @@ const BackendContext = createContext<BackendContextType>({
 
 export function useBackendContextState(): BackendContextType {
   const [url, triedToLog, changeBackend, resetBackend] = useBackendURL();
-  const [token, updateToken] = useBackendDefaultToken();
+  const [token, _updateToken] = useBackendDefaultToken();
+  const updateToken = (t?:string) => {
+    // console.log("update token", t)
+    _updateToken(t)
+  }
 
   const tokenCleaner = useCallback(() => { updateToken(undefined) }, [])
   const [cleaners, setCleaners] = useState([tokenCleaner])
diff --git a/packages/frontend/src/context/instance.ts 
b/packages/frontend/src/context/instance.ts
index 0edfbfe..fecf364 100644
--- a/packages/frontend/src/context/instance.ts
+++ b/packages/frontend/src/context/instance.ts
@@ -26,6 +26,7 @@ interface Type {
   id: string;
   token?: string;
   admin?: boolean;
+  changeToken: (t?:string) => void;
 }
 
 const Context = createContext<Type>({} as any)
diff --git a/packages/frontend/src/declaration.d.ts 
b/packages/frontend/src/declaration.d.ts
index 04a2e2d..6717566 100644
--- a/packages/frontend/src/declaration.d.ts
+++ b/packages/frontend/src/declaration.d.ts
@@ -51,6 +51,51 @@ type Amount = string;
 type UUID = string;
 type Integer = number;
 
+export namespace ExchangeBackend {
+    interface WireResponse {
+
+        // Master public key of the exchange, must match the key returned in 
/keys.
+        master_public_key: EddsaPublicKey;
+      
+        // Array of wire accounts operated by the exchange for
+        // incoming wire transfers.
+        accounts: WireAccount[];
+      
+        // Object mapping names of wire methods (i.e. "sepa" or "x-taler-bank")
+        // to wire fees.
+        fees: { method : AggregateTransferFee };
+      }
+      interface WireAccount {
+        // payto:// URI identifying the account and wire method
+        payto_uri: string;
+      
+        // Signature using the exchange's offline key
+        // with purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS.
+        master_sig: EddsaSignature;
+      }
+      interface AggregateTransferFee {
+        // Per transfer wire transfer fee.
+        wire_fee: Amount;
+      
+        // Per transfer closing fee.
+        closing_fee: Amount;
+      
+        // What date (inclusive) does this fee go into effect?
+        // The different fees must cover the full time period in which
+        // any of the denomination keys are valid without overlap.
+        start_date: Timestamp;
+      
+        // What date (exclusive) does this fee stop going into effect?
+        // The different fees must cover the full time period in which
+        // any of the denomination keys are valid without overlap.
+        end_date: Timestamp;
+      
+        // Signature of TALER_MasterWireFeePS with
+        // purpose TALER_SIGNATURE_MASTER_WIRE_FEES.
+        sig: EddsaSignature;
+      }
+      
+}
 export namespace MerchantBackend {
     interface ErrorDetail {
 
diff --git a/packages/frontend/src/hooks/instance.ts 
b/packages/frontend/src/hooks/instance.ts
index 4bb6410..2e5c6c2 100644
--- a/packages/frontend/src/hooks/instance.ts
+++ b/packages/frontend/src/hooks/instance.ts
@@ -21,7 +21,7 @@ import { useInstanceContext } from '../context/instance';
 
 
 interface InstanceAPI {
-  updateInstance: (data: 
MerchantBackend.Instances.InstanceReconfigurationMessage, a?: 
MerchantBackend.Instances.InstanceAuthConfigurationMessage) => Promise<void>;
+  updateInstance: (data: 
MerchantBackend.Instances.InstanceReconfigurationMessage) => Promise<void>;
   deleteInstance: () => Promise<void>;
   clearToken: () => Promise<void>;
   setNewToken: (token: string) => Promise<void>;
@@ -33,19 +33,13 @@ export function useInstanceAPI(): InstanceAPI {
 
   const url = !admin ? baseUrl : `${baseUrl}/instances/${id}`
 
-  const updateInstance = async (instance: 
MerchantBackend.Instances.InstanceReconfigurationMessage, auth?: 
MerchantBackend.Instances.InstanceAuthConfigurationMessage): Promise<void> => {
+  const updateInstance = async (instance: 
MerchantBackend.Instances.InstanceReconfigurationMessage): Promise<void> => {
     await request(`${url}/private/`, {
       method: 'patch',
       token,
       data: instance
     })
 
-    if (auth) await request(`${url}/private/auth`, {
-      method: 'post',
-      token,
-      data: auth
-    })
-
     if (adminToken) mutate(['/private/instances', adminToken, baseUrl], null)
     mutate([`/private/`, token, url], null)
   };
diff --git a/packages/frontend/src/i18n/index.tsx 
b/packages/frontend/src/i18n/index.tsx
index fb8250b..5be1a3b 100644
--- a/packages/frontend/src/i18n/index.tsx
+++ b/packages/frontend/src/i18n/index.tsx
@@ -135,7 +135,7 @@ export function Translate({ children }: TranslateProps): 
VNode {
   const ctx = useTranslationContext()
   const translation: string = ctx.handler.ngettext(s, s, 1);
   const result = getTranslatedChildren(translation, children)
-  return <>{result}</>;
+  return <Fragment>{result}</Fragment>;
 }
 
 /**
@@ -188,7 +188,7 @@ export function TranslatePlural({ children, target }: 
TranslationPluralProps): V
   const ctx = useTranslationContext()
   const translation = ctx.handler.ngettext(s, s, 1);
   const result = getTranslatedChildren(translation, children);
-  return <>{result}</>;
+  return <Fragment>{result}</Fragment>;
 }
 
 /**
@@ -199,6 +199,6 @@ export function TranslateSingular({ children, target }: 
TranslationPluralProps):
   const ctx = useTranslationContext()
   const translation = ctx.handler.ngettext(s, s, target);
   const result = getTranslatedChildren(translation, children);
-  return <>{result}</>;
+  return <Fragment>{result}</Fragment>;
 
 }
diff --git a/packages/frontend/src/paths/admin/create/CreatePage.tsx 
b/packages/frontend/src/paths/admin/create/CreatePage.tsx
index 4cf4b3c..aa46f2f 100644
--- a/packages/frontend/src/paths/admin/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/admin/create/CreatePage.tsx
@@ -56,7 +56,6 @@ function with_defaults(id?: string): Partial<Entity> {
 
 export function CreatePage({ onCreate, onBack, forceId }: Props): VNode {
   const [value, valueHandler] = useState(with_defaults(forceId))
-  // const [errors, setErrors] = useState<FormErrors<Entity>>({})
 
   let errors: FormErrors<Entity> = {}
   try {
@@ -86,7 +85,7 @@ export function CreatePage({ onCreate, onBack, forceId }: 
Props): VNode {
         <div class="column is-two-thirds">
           <FormProvider<Entity> errors={errors} object={value} 
valueHandler={valueHandler} >
 
-            <InputWithAddon<Entity> name="id" label={i18n`ID`} 
addonBefore={`${backend.url}/private/instances/`} readonly={!!forceId} 
tooltip={i18n`unique name identification`} />
+            <InputWithAddon<Entity> name="id" label={i18n`ID`} 
addonBefore={`${backend.url}/private/instances/`} readonly={!!forceId} 
tooltip={i18n`display name identification`} />
 
             <Input<Entity> name="name" label={i18n`Name`} 
tooltip={i18n`descriptive name`} />
 
diff --git a/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx 
b/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
index 140d0c5..9d22fc8 100644
--- a/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
@@ -88,43 +88,45 @@ interface Entity {
 
 export function CreatePage({ onCreate, onBack }: Props): VNode {
   const [value, valueHandler] = useState(with_defaults())
-  const [errors, setErrors] = useState<FormErrors<Entity>>({})
+  // const [errors, setErrors] = useState<FormErrors<Entity>>({})
 
   const inventoryList = Object.values(value.inventoryProducts)
   const productList = Object.values(value.products)
 
+  let errors: FormErrors<Entity> = {}
+  try {
+    schema.validateSync(value, { abortEarly: false })
+  } catch (err) {
+    const yupErrors = err.inner as yup.ValidationError[]
+    errors = yupErrors.reduce((prev, cur) => !cur.path ? prev : ({ ...prev, 
[cur.path]: cur.message }), {})
+  }
+  const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== 
undefined)
+
   const submit = (): void => {
-    try {
-      schema.validateSync(value, { abortEarly: false })
-      const order = schema.cast(value)
-
-      const request: MerchantBackend.Orders.PostOrderRequest = {
-        order: {
-          amount: order.pricing.order_price,
-          summary: order.pricing.summary,
-          products: productList,
-          extra: value.extra,
-          pay_deadline: value.payments.pay_deadline ? { t_ms: 
Math.floor(value.payments.pay_deadline.getTime() / 1000) * 1000 } : undefined,
-          wire_transfer_deadline: value.payments.pay_deadline ? { t_ms: 
Math.floor(value.payments.pay_deadline.getTime() / 1000) * 1000 } : undefined,
-          refund_deadline: value.payments.refund_deadline ? { t_ms: 
Math.floor(value.payments.refund_deadline.getTime() / 1000) * 1000 } : 
undefined,
-          max_fee: value.payments.max_fee,
-          max_wire_fee: value.payments.max_wire_fee,
-          delivery_date: value.payments.delivery_date ? { t_ms: 
value.payments.delivery_date.getTime() } : undefined,
-          delivery_location: value.payments.delivery_location,
-          fulfillment_url: value.payments.fullfilment_url,
-        },
-        inventory_products: inventoryList.map(p => ({
-          product_id: p.product.id,
-          quantity: p.quantity
-        })),
-      }
-
-      onCreate(request);
-    } catch (err) {
-      const errors = err.inner as yup.ValidationError[]
-      const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({ 
...prev, [cur.path]: cur.message }), {})
-      setErrors(pathMessages)
+    const order = schema.cast(value)
+
+    const request: MerchantBackend.Orders.PostOrderRequest = {
+      order: {
+        amount: order.pricing.order_price,
+        summary: order.pricing.summary,
+        products: productList,
+        extra: value.extra,
+        pay_deadline: value.payments.pay_deadline ? { t_ms: 
Math.floor(value.payments.pay_deadline.getTime() / 1000) * 1000 } : undefined,
+        wire_transfer_deadline: value.payments.pay_deadline ? { t_ms: 
Math.floor(value.payments.pay_deadline.getTime() / 1000) * 1000 } : undefined,
+        refund_deadline: value.payments.refund_deadline ? { t_ms: 
Math.floor(value.payments.refund_deadline.getTime() / 1000) * 1000 } : 
undefined,
+        max_fee: value.payments.max_fee,
+        max_wire_fee: value.payments.max_wire_fee,
+        delivery_date: value.payments.delivery_date ? { t_ms: 
value.payments.delivery_date.getTime() } : undefined,
+        delivery_location: value.payments.delivery_location,
+        fulfillment_url: value.payments.fullfilment_url,
+      },
+      inventory_products: inventoryList.map(p => ({
+        product_id: p.product.id,
+        quantity: p.quantity
+      })),
     }
+
+    onCreate(request);
   }
 
   const config = useConfigContext()
@@ -313,7 +315,7 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
 
           <div class="buttons is-right mt-5">
             {onBack && <button class="button" onClick={onBack} 
><Translate>Cancel</Translate></button>}
-            <button class="button is-success" onClick={submit} 
><Translate>Confirm</Translate></button>
+            <button class="button is-success" onClick={submit} 
disabled={hasErrors} ><Translate>Confirm</Translate></button>
           </div>
 
         </div>
diff --git a/packages/frontend/src/paths/instance/orders/create/index.tsx 
b/packages/frontend/src/paths/instance/orders/create/index.tsx
index 01d2e6c..ee0577a 100644
--- a/packages/frontend/src/paths/instance/orders/create/index.tsx
+++ b/packages/frontend/src/paths/instance/orders/create/index.tsx
@@ -42,11 +42,6 @@ export default function OrderCreate({ onConfirm, onBack }: 
Props): VNode {
 
 
   return <Fragment>
-    <NotificationCard notification={{
-      message: 'DEMO',
-      type: 'WARN',
-      description: 'this can be created as a popup or be expanded with more 
options'
-    }} />
     
     <NotificationCard notification={notif} />
 
diff --git a/packages/frontend/src/paths/instance/products/list/Table.tsx 
b/packages/frontend/src/paths/instance/products/list/Table.tsx
index 85e5ce4..878506d 100644
--- a/packages/frontend/src/paths/instance/products/list/Table.tsx
+++ b/packages/frontend/src/paths/instance/products/list/Table.tsx
@@ -242,7 +242,7 @@ function EmptyTable(): VNode {
     <p>
       <span class="icon is-large"><i class="mdi mdi-emoticon-sad mdi-48px" 
/></span>
     </p>
-    <p><Translate>There is no instances yet, add more pressing the + 
sign</Translate></p>
+    <p><Translate>There is no products yet, add more pressing the + 
sign</Translate></p>
   </div>
 }
 
diff --git 
a/packages/frontend/src/paths/instance/reserves/create/CreatePage.tsx 
b/packages/frontend/src/paths/instance/reserves/create/CreatePage.tsx
index 6c079e6..a98e665 100644
--- a/packages/frontend/src/paths/instance/reserves/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/instance/reserves/create/CreatePage.tsx
@@ -19,14 +19,18 @@
 * @author Sebastian Javier Marchano (sebasjm)
 */
 
-import { h, VNode } from "preact";
-import { useState } from "preact/hooks";
+import { Fragment, h, VNode } from "preact";
+import { StateUpdater, useEffect, useState } from "preact/hooks";
 import { FormErrors, FormProvider } from 
"../../../../components/form/FormProvider";
 import { Input } from "../../../../components/form/Input";
 import { InputCurrency } from "../../../../components/form/InputCurrency";
-import { MerchantBackend } from "../../../../declaration";
+import { ExchangeBackend, MerchantBackend } from "../../../../declaration";
 import { Translate, useTranslator } from "../../../../i18n";
 import { AsyncButton } from "../../../../components/exception/AsyncButton";
+import { canonicalizeBaseUrl, ExchangeKeysJson } from "@gnu-taler/taler-util"
+import { PAYTO_WIRE_METHOD_LOOKUP, URL_REGEX } from 
"../../../../utils/constants";
+import { request } from "../../../../hooks/backend";
+import { InputSelector } from "../../../../components/form/InputSelector";
 
 type Entity = MerchantBackend.Tips.ReserveCreateRequest
 
@@ -36,38 +40,122 @@ interface Props {
 }
 
 
-export function CreatePage({ onCreate, onBack }: Props): VNode {
-  const [reserve, setReserve] = useState<Partial<Entity>>({})
+enum Steps {
+  EXCHANGE,
+  WIRE_METHOD,
+}
+
+interface ViewProps {
+  step : Steps,
+  setCurrentStep: (s:Steps) => void;
+  reserve: Partial<Entity>;
+  onBack?: () => void;
+  submitForm: () => Promise<void>;
+  setReserve: StateUpdater<Partial<Entity>>;
+}
+function ViewStep({ step, setCurrentStep, reserve, onBack, submitForm, 
setReserve }: ViewProps): VNode {
   const i18n = useTranslator()
+  const [wireMethods, setWireMethods] = useState<Array<string>>([])
+  const [exchangeQueryError, setExchangeQueryError] = 
useState<string|undefined>(undefined)
+
+  useEffect(() => {
+    setExchangeQueryError(undefined)
+  }, [reserve.exchange_url])
+
+  switch (step) {
+    case Steps.EXCHANGE: {
+      const errors: FormErrors<Entity> = {
+        initial_balance: !reserve.initial_balance ? 'cannot be empty' : 
!(parseInt(reserve.initial_balance.split(':')[1], 10) > 0) ? i18n`it should be 
greater than 0` : undefined,
+        exchange_url: !reserve.exchange_url ? i18n`cannot be empty` : 
!URL_REGEX.test(reserve.exchange_url) ? i18n`must be a valid URL` : 
!!exchangeQueryError ? exchangeQueryError : undefined,
+      }
+
+      const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== 
undefined)
+
+      return <Fragment>
+        <FormProvider<Entity> object={reserve} errors={errors} 
valueHandler={setReserve}>
+          <InputCurrency<Entity> name="initial_balance" label={i18n`Initial 
balance`} />
+          <Input<Entity> name="exchange_url" label={i18n`Exchange URL`} />
+        </FormProvider>
+
+        <div class="buttons is-right mt-5">
+          {onBack && <button class="button" onClick={onBack} 
><Translate>Cancel</Translate></button>}
+          <AsyncButton onClick={() => {
+            return 
request<ExchangeBackend.WireResponse>(`${reserve.exchange_url}wire`).then(r => {
+              const wireMethods = r.data.accounts.map(a => {
+                const match = PAYTO_WIRE_METHOD_LOOKUP.exec(a.payto_uri)
+                return match && match[1] || ''
+              })
+              setWireMethods(wireMethods)
+              setCurrentStep(Steps.WIRE_METHOD)
+              return 
+            }).catch((r: any) => {
+              setExchangeQueryError(r.message)
+            })
+          }} disabled={hasErrors} ><Translate>Next</Translate></AsyncButton>
+        </div>
+      </Fragment>
+    }
 
-  const errors: FormErrors<Entity> = {
-    initial_balance: !reserve.initial_balance ? 'cannot be empty' : 
!(parseInt(reserve.initial_balance.split(':')[1], 10) > 0) ? i18n`it should be 
greater than 0` : undefined,
-    exchange_url: !reserve.exchange_url ? i18n`cannot be empty` : undefined,
-    wire_method: !reserve.wire_method ? i18n`cannot be empty` : undefined,
+    case Steps.WIRE_METHOD: {
+      const errors: FormErrors<Entity> = {
+        wire_method: !reserve.wire_method ? i18n`cannot be empty` : undefined,
+      }
+
+      const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== 
undefined)
+      return <Fragment>
+        <FormProvider<Entity> object={reserve} errors={errors} 
valueHandler={setReserve}>
+          <InputCurrency<Entity> name="initial_balance" label={i18n`Initial 
balance`} readonly />
+          <Input<Entity> name="exchange_url" label={i18n`Exchange URL`} 
readonly />
+          <InputSelector<Entity> name="wire_method" label={i18n`Wire method`} 
values={wireMethods} placeholder={i18n`Select one wire method`}/>
+        </FormProvider>
+        <div class="buttons is-right mt-5">
+          {onBack && <button class="button" onClick={() => 
setCurrentStep(Steps.EXCHANGE)} ><Translate>Back</Translate></button>}
+          <AsyncButton onClick={submitForm} disabled={hasErrors} 
><Translate>Confirm</Translate></AsyncButton>
+        </div>
+      </Fragment>
+
+    }
   }
+}
+
+export function CreatePage({ onCreate, onBack }: Props): VNode {
+  const [reserve, setReserve] = useState<Partial<Entity>>({})
 
-  const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== 
undefined)
 
   const submitForm = () => {
-    if (hasErrors) return Promise.reject()
     return onCreate(reserve as Entity)
   }
 
+  const [currentStep, setCurrentStep] = useState(Steps.EXCHANGE)
+
+
   return <div>
     <section class="section is-main-section">
       <div class="columns">
         <div class="column" />
         <div class="column is-two-thirds">
-          <FormProvider<Entity> object={reserve} errors={errors} 
valueHandler={setReserve}>
-            <InputCurrency<Entity> name="initial_balance" label={i18n`Initial 
balance`} />
-            <Input<Entity> name="exchange_url" label={i18n`Exchange`} />
-            <Input<Entity> name="wire_method" label={i18n`Wire method`} />
-          </FormProvider>
-
-          <div class="buttons is-right mt-5">
-            {onBack && <button class="button" onClick={onBack} 
><Translate>Cancel</Translate></button>}
-            <AsyncButton onClick={submitForm} disabled={hasErrors} 
><Translate>Confirm</Translate></AsyncButton>
+
+          <div class="tabs is-toggle is-fullwidth is-small">
+            <ul>
+              <li class={currentStep === Steps.EXCHANGE?"is-active":""}>
+                <a style={{ cursor: 'initial' }}>
+                  <span>Set exchange</span>
+                </a>
+              </li>
+              <li class={currentStep === Steps.WIRE_METHOD?"is-active":""}>
+                <a style={{ cursor: 'initial' }}>
+                  <span>Set wire method</span>
+                </a>
+              </li>
+            </ul>
           </div>
+
+          <ViewStep step={currentStep} reserve={reserve} 
+            setCurrentStep={setCurrentStep}
+            setReserve={setReserve}
+            submitForm={submitForm}
+            onBack={onBack}
+           />
         </div>
         <div class="column" />
       </div>
diff --git 
a/packages/frontend/src/paths/instance/reserves/create/CreatedSuccessfully.tsx 
b/packages/frontend/src/paths/instance/reserves/create/CreatedSuccessfully.tsx
index af27c5b..2deae14 100644
--- 
a/packages/frontend/src/paths/instance/reserves/create/CreatedSuccessfully.tsx
+++ 
b/packages/frontend/src/paths/instance/reserves/create/CreatedSuccessfully.tsx
@@ -16,8 +16,9 @@
 import { h, VNode } from "preact";
 import { CreatedSuccessfully as Template } from 
"../../../../components/notifications/CreatedSuccessfully";
 import { MerchantBackend } from "../../../../declaration";
+import { Translate } from "../../../../i18n";
 
-type Entity = MerchantBackend.Tips.ReserveCreateConfirmation;
+type Entity = {request: MerchantBackend.Tips.ReserveCreateRequest, response: 
MerchantBackend.Tips.ReserveCreateConfirmation};
 
 interface Props {
   entity: Entity;
@@ -28,6 +29,30 @@ interface Props {
 export function CreatedSuccessfully({ entity, onConfirm, onCreateAnother }: 
Props): VNode {
 
   return <Template onConfirm={onConfirm} onCreateAnother={onCreateAnother}>
+    <div class="field is-horizontal">
+      <div class="field-label is-normal">
+        <label class="label">Exchange</label>
+      </div>
+      <div class="field-body is-flex-grow-3">
+        <div class="field">
+          <p class="control">
+            <input readonly class="input" value={entity.request.exchange_url} 
/>
+          </p>
+        </div>
+      </div>
+    </div>
+    <div class="field is-horizontal">
+      <div class="field-label is-normal">
+        <label class="label">Amount</label>
+      </div>
+      <div class="field-body is-flex-grow-3">
+        <div class="field">
+          <p class="control">
+            <input readonly class="input" 
value={entity.request.initial_balance} />
+          </p>
+        </div>
+      </div>
+    </div>
     <div class="field is-horizontal">
       <div class="field-label is-normal">
         <label class="label">Account address</label>
@@ -35,22 +60,28 @@ export function CreatedSuccessfully({ entity, onConfirm, 
onCreateAnother }: Prop
       <div class="field-body is-flex-grow-3">
         <div class="field">
           <p class="control">
-            <input readonly class="input" value={entity.payto_uri} />
+            <input readonly class="input" value={entity.response.payto_uri} />
           </p>
         </div>
       </div>
     </div>
     <div class="field is-horizontal">
       <div class="field-label is-normal">
-        <label class="label">Message</label>
+        <label class="label">Subject</label>
       </div>
       <div class="field-body is-flex-grow-3">
         <div class="field">
           <p class="control">
-            <input class="input" readonly value={entity.reserve_pub} />
+            <input class="input" readonly value={entity.response.reserve_pub} 
/>
           </p>
         </div>
       </div>
     </div>
+    <p class="is-size-5"><Translate>Now you should transfer to the exchange 
into the account address indicated above and the transaction must carry the 
subject message.</Translate></p>
+
+    <p class="is-size-5"><Translate>For example:</Translate></p>
+    <pre>
+    
{entity.response.payto_uri}?message={entity.response.reserve_pub}&amount={entity.request.initial_balance}
+    </pre>
   </Template>;
 }
diff --git a/packages/frontend/src/paths/instance/reserves/create/index.tsx 
b/packages/frontend/src/paths/instance/reserves/create/index.tsx
index ad990e9..eb16f85 100644
--- a/packages/frontend/src/paths/instance/reserves/create/index.tsx
+++ b/packages/frontend/src/paths/instance/reserves/create/index.tsx
@@ -28,7 +28,6 @@ import { useTranslator } from '../../../../i18n';
 import { Notification } from '../../../../utils/types';
 import { CreatedSuccessfully } from './CreatedSuccessfully';
 import { CreatePage } from './CreatePage';
-
 interface Props {
   onBack: () => void;
   onConfirm: () => void;
@@ -38,7 +37,10 @@ export default function CreateReserve({ onBack, onConfirm }: 
Props): VNode {
   const [notif, setNotif] = useState<Notification | undefined>(undefined)
   const i18n = useTranslator()
 
-  const [createdOk, setCreatedOk] = 
useState<MerchantBackend.Tips.ReserveCreateConfirmation | undefined>(undefined);
+  const [createdOk, setCreatedOk] = useState<{
+    request: MerchantBackend.Tips.ReserveCreateRequest,
+    response: MerchantBackend.Tips.ReserveCreateConfirmation
+  } | undefined>(undefined);
 
   if (createdOk) {
     return <CreatedSuccessfully entity={createdOk} onConfirm={onConfirm} />
@@ -49,7 +51,7 @@ export default function CreateReserve({ onBack, onConfirm }: 
Props): VNode {
     <CreatePage
       onBack={onBack}
       onCreate={(request: MerchantBackend.Tips.ReserveCreateRequest) => {
-        return createReserve(request).then((r) => 
setCreatedOk(r.data)).catch((error) => {
+        return createReserve(request).then((r) => setCreatedOk({ request, 
response: r.data })).catch((error) => {
           setNotif({
             message: i18n`could not create reserve`,
             type: "ERROR",
diff --git 
a/packages/frontend/src/paths/instance/reserves/details/DetailPage.tsx 
b/packages/frontend/src/paths/instance/reserves/details/DetailPage.tsx
index 3771337..08b463a 100644
--- a/packages/frontend/src/paths/instance/reserves/details/DetailPage.tsx
+++ b/packages/frontend/src/paths/instance/reserves/details/DetailPage.tsx
@@ -44,9 +44,9 @@ interface Props {
   selected: Entity;
 }
 
-export function DetailPage({ selected }: Props): VNode {
+export function DetailPage({ selected, onBack }: Props): VNode {
   const i18n = useTranslator()
-  return <Fragment>
+  return <div class="section main-section">
     <FormProvider object={selected} valueHandler={null} >
       <InputDate<Entity> name="creation_time" label={i18n`Created at`} 
readonly />
       <InputDate<Entity> name="expiration_time" label={i18n`Valid until`} 
readonly />
@@ -58,7 +58,17 @@ export function DetailPage({ selected }: Props): VNode {
     {selected.tips && selected.tips.length > 0 ? <Table tips={selected.tips} 
/> : <div>
       no tips for this reserve
     </div>}
-  </Fragment>
+    <div class="columns">
+      <div class="column" />
+      <div class="column is-two-thirds">
+        <div class="buttons is-right mt-5">
+          <button class="button" 
onClick={onBack}><Translate>Back</Translate></button>
+        </div>
+      </div>
+      <div class="column" />
+    </div>
+
+  </div>
 }
 
 async function copyToClipboard(text: string) {
@@ -101,10 +111,10 @@ function TipRow({ id, entry }: { id: string, entry: 
MerchantBackend.Tips.TipStat
   }
   if (!result.ok) {
     return <tr>
-      <td>...</td>
-      <td>{entry.total_amount}</td>
+      <td>...</td> {/* authorized */}
       <td>{entry.total_amount}</td>
-      <td>expired</td>
+      <td>{entry.reason}</td>
+      <td>...</td> {/* expired */}
     </tr>
   }
   const info = result.data
diff --git 
a/packages/frontend/src/paths/instance/reserves/list/AutorizeTipModal.tsx 
b/packages/frontend/src/paths/instance/reserves/list/AutorizeTipModal.tsx
index 42847ff..6f97815 100644
--- a/packages/frontend/src/paths/instance/reserves/list/AutorizeTipModal.tsx
+++ b/packages/frontend/src/paths/instance/reserves/list/AutorizeTipModal.tsx
@@ -44,19 +44,20 @@ export function AuthorizeTipModal({ onCancel, onConfirm, 
tipAuthorized }: Author
   type State = MerchantBackend.Tips.TipCreateRequest
   const [form, setValue] = useState<Partial<State>>({})
   const i18n = useTranslator();
-  const [errors, setErrors] = useState<FormErrors<State>>({})
 
-  const validateAndConfirm = () => {
-    try {
-      AuthorizeTipSchema.validateSync(form, { abortEarly: false })
-      onConfirm(form as State)
-    } catch (err) {
-      const errors = err.inner as any[]
-      const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({ 
...prev, [cur.path]: cur.message }), {})
-      setErrors(pathMessages)
-    }
+  // const [errors, setErrors] = useState<FormErrors<State>>({})
+  let errors: FormErrors<State> = {}
+  try {
+    AuthorizeTipSchema.validateSync(form, { abortEarly: false })
+  } catch (err) {
+    const yupErrors = err.inner as any[]
+    errors = yupErrors.reduce((prev, cur) => !cur.path ? prev : ({ ...prev, 
[cur.path]: cur.message }), {})
   }
+  const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== 
undefined)
 
+  const validateAndConfirm = () => {
+    onConfirm(form as State)
+  }
   if (tipAuthorized) {
     return <ContinueModal description="tip" active onConfirm={onCancel}>
       <CreatedSuccessfully
@@ -67,7 +68,7 @@ export function AuthorizeTipModal({ onCancel, onConfirm, 
tipAuthorized }: Author
     </ContinueModal>
   }
 
-  return <ConfirmModal description="tip" active onCancel={onCancel} 
onConfirm={validateAndConfirm}>
+  return <ConfirmModal description="tip" active onCancel={onCancel} 
disabled={hasErrors} onConfirm={validateAndConfirm}>
 
     <FormProvider<State> errors={errors} object={form} valueHandler={setValue} 
>
       <InputCurrency<State> name="amount" label={i18n`Amount`} 
tooltip={i18n`amount of tip`}/>
diff --git a/packages/frontend/src/paths/instance/reserves/list/Table.tsx 
b/packages/frontend/src/paths/instance/reserves/list/Table.tsx
index 58a2df5..a21de64 100644
--- a/packages/frontend/src/paths/instance/reserves/list/Table.tsx
+++ b/packages/frontend/src/paths/instance/reserves/list/Table.tsx
@@ -14,10 +14,10 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
- /**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
 
 import { format } from "date-fns"
 import { Fragment, h, VNode } from "preact"
@@ -37,23 +37,7 @@ interface Props {
   selected?: boolean;
 }
 
-export function CardTable({ instances, onCreate, onSelect, onNewTip, onDelete, 
selected }: Props): VNode {
-  const [actionQueue, actionQueueHandler] = useState<Actions<Entity>[]>([]);
-  const [rowSelection, rowSelectionHandler] = useState<string[]>([])
-
-  useEffect(() => {
-    if (actionQueue.length > 0 && !selected && actionQueue[0].type == 
'DELETE') {
-      onDelete(actionQueue[0].element)
-      actionQueueHandler(actionQueue.slice(1))
-    }
-  }, [actionQueue, selected, onDelete])
-
-  useEffect(() => {
-    if (actionQueue.length > 0 && !selected && actionQueue[0].type == 
'UPDATE') {
-      onNewTip(actionQueue[0].element)
-      actionQueueHandler(actionQueue.slice(1))
-    }
-  }, [actionQueue, selected, onNewTip])
+export function CardTable({ instances, onCreate, onSelect, onNewTip, onDelete 
}: Props): VNode {
 
   const [withoutFunds, withFunds] = instances.reduce((prev, current) => {
     const amount = current.exchange_initial_amount
@@ -63,118 +47,91 @@ export function CardTable({ instances, onCreate, onSelect, 
onNewTip, onDelete, s
       prev[1] = prev[1].concat(current)
     }
     return prev
-  }, new Array<Array<Entity>>([],[]))
+  }, new Array<Array<Entity>>([], []))
 
 
   return <Fragment>
     {withoutFunds.length > 0 && <div class="card has-table">
-    <header class="card-header">
-      <p class="card-header-title"><span class="icon"><i class="mdi mdi-cash" 
/></span><Translate>Reserves not yet funded</Translate></p>
-
-      <div class="card-header-icon" aria-label="more options">
-
-        <button class={rowSelection.length > 0 ? "button is-danger" : 
"is-hidden"}
-          type="button" onClick={(): void => 
actionQueueHandler(buildActions(instances, rowSelection, 'DELETE'))} >
-          Delete
-        </button>
-      </div>
-      <div class="card-header-icon" aria-label="more options">
-        <button class="button is-info" type="button" onClick={onCreate}>
-          <span class="icon is-small" ><i class="mdi mdi-plus mdi-36px" 
/></span>
-        </button>
-      </div>
-
-    </header>
-    <div class="card-content">
-      <div class="b-table has-pagination">
-        <div class="table-wrapper has-mobile-cards">
-          <TableWithoutFund instances={withoutFunds} onNewTip={onNewTip} 
onSelect={onSelect} onDelete={onDelete} rowSelection={rowSelection} 
rowSelectionHandler={rowSelectionHandler} />
+      <header class="card-header">
+        <p class="card-header-title"><span class="icon"><i class="mdi 
mdi-cash" /></span><Translate>Reserves not yet funded</Translate></p>
+        <div class="card-header-icon" aria-label="more options">
+          <button class="button is-info" type="button" onClick={onCreate}>
+            <span class="icon is-small" ><i class="mdi mdi-plus mdi-36px" 
/></span>
+          </button>
+        </div>
+      </header>
+      <div class="card-content">
+        <div class="b-table has-pagination">
+          <div class="table-wrapper has-mobile-cards">
+            <TableWithoutFund instances={withoutFunds} onNewTip={onNewTip} 
onSelect={onSelect} onDelete={onDelete} />
+          </div>
         </div>
       </div>
-    </div>
-  </div> }
-
-  <div class="card has-table">
-    <header class="card-header">
-      <p class="card-header-title"><span class="icon"><i class="mdi mdi-cash" 
/></span><Translate>Reserves ready</Translate></p>
-
-      <div class="card-header-icon" aria-label="more options">
-
-        <button class={rowSelection.length > 0 ? "button is-danger" : 
"is-hidden"}
-          type="button" onClick={(): void => 
actionQueueHandler(buildActions(instances, rowSelection, 'DELETE'))} >
-          Delete
-        </button>
-      </div>
-      <div class="card-header-icon" aria-label="more options">
-        <button class="button is-info" type="button" onClick={onCreate}>
-          <span class="icon is-small" ><i class="mdi mdi-plus mdi-36px" 
/></span>
-        </button>
-      </div>
+    </div>}
 
-    </header>
-    <div class="card-content">
-      <div class="b-table has-pagination">
-        <div class="table-wrapper has-mobile-cards">
-          {withFunds.length > 0 ?
-            <Table instances={withFunds} onNewTip={onNewTip} 
onSelect={onSelect} onDelete={onDelete} rowSelection={rowSelection} 
rowSelectionHandler={rowSelectionHandler} /> :
-            <EmptyTable />
-          }
+    <div class="card has-table">
+      <header class="card-header">
+        <p class="card-header-title"><span class="icon"><i class="mdi 
mdi-cash" /></span><Translate>Reserves ready</Translate></p>
+        <div class="card-header-icon" aria-label="more options">
+        </div>
+      </header>
+      <div class="card-content">
+        <div class="b-table has-pagination">
+          <div class="table-wrapper has-mobile-cards">
+            {withFunds.length > 0 ?
+              <Table instances={withFunds} onNewTip={onNewTip} 
onSelect={onSelect} onDelete={onDelete} /> :
+              <EmptyTable />
+            }
+          </div>
         </div>
       </div>
     </div>
-  </div>
   </Fragment>
 }
 interface TableProps {
-  rowSelection: string[];
   instances: Entity[];
   onNewTip: (id: Entity) => void;
   onDelete: (id: Entity) => void;
   onSelect: (id: Entity) => void;
-  rowSelectionHandler: StateUpdater<string[]>;
 }
 
-function toggleSelected<T>(id: T): (prev: T[]) => T[] {
-  return (prev: T[]): T[] => prev.indexOf(id) == -1 ? [...prev, id] : 
prev.filter(e => e != id)
-}
-
-function Table({ rowSelection, rowSelectionHandler, instances, onNewTip, 
onSelect, onDelete }: TableProps): VNode {
+function Table({ instances, onNewTip, onSelect, onDelete }: TableProps): VNode 
{
   return (
     <div class="table-container">
-    <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
-      <thead>
-        <tr>
-          <th><Translate>Created at</Translate></th>
-          <th><Translate>Expires at</Translate></th>
-          <th><Translate>Initial</Translate></th>
-          <th><Translate>Picked up</Translate></th>
-          <th><Translate>Committed</Translate></th>
-          <th />
-        </tr>
-      </thead>
-      <tbody>
-        {instances.map(i => {
-          return <tr key={i.id}>
-            <td onClick={(): void => onSelect(i)} style={{cursor: 'pointer'}} 
>{i.creation_time.t_ms === "never" ? "never" : format(i.creation_time.t_ms, 
'yyyy/MM/dd HH:mm:ss')}</td>
-            <td onClick={(): void => onSelect(i)} style={{cursor: 'pointer'}} 
>{i.expiration_time.t_ms === "never" ? "never" : format(i.expiration_time.t_ms, 
'yyyy/MM/dd HH:mm:ss')}</td>
-            <td onClick={(): void => onSelect(i)} style={{cursor: 'pointer'}} 
>{i.exchange_initial_amount}</td>
-            <td onClick={(): void => onSelect(i)} style={{cursor: 'pointer'}} 
>{i.pickup_amount}</td>
-            <td onClick={(): void => onSelect(i)} style={{cursor: 'pointer'}} 
>{i.committed_amount}</td>
-            <td class="is-actions-cell right-sticky">
-              <div class="buttons is-right">
-                <button class="button is-small is-danger jb-modal" 
type="button" onClick={(): void => onDelete(i)}>
-                  Delete
+      <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
+        <thead>
+          <tr>
+            <th><Translate>Created at</Translate></th>
+            <th><Translate>Expires at</Translate></th>
+            <th><Translate>Initial</Translate></th>
+            <th><Translate>Picked up</Translate></th>
+            <th><Translate>Committed</Translate></th>
+            <th />
+          </tr>
+        </thead>
+        <tbody>
+          {instances.map(i => {
+            return <tr key={i.id}>
+              <td onClick={(): void => onSelect(i)} style={{ cursor: 'pointer' 
}} >{i.creation_time.t_ms === "never" ? "never" : format(i.creation_time.t_ms, 
'yyyy/MM/dd HH:mm:ss')}</td>
+              <td onClick={(): void => onSelect(i)} style={{ cursor: 'pointer' 
}} >{i.expiration_time.t_ms === "never" ? "never" : 
format(i.expiration_time.t_ms, 'yyyy/MM/dd HH:mm:ss')}</td>
+              <td onClick={(): void => onSelect(i)} style={{ cursor: 'pointer' 
}} >{i.exchange_initial_amount}</td>
+              <td onClick={(): void => onSelect(i)} style={{ cursor: 'pointer' 
}} >{i.pickup_amount}</td>
+              <td onClick={(): void => onSelect(i)} style={{ cursor: 'pointer' 
}} >{i.committed_amount}</td>
+              <td class="is-actions-cell right-sticky">
+                <div class="buttons is-right">
+                  <button class="button is-small is-danger jb-modal" 
type="button" onClick={(): void => onDelete(i)}>
+                    Delete
                 </button>
-                <button class="button is-small is-info jb-modal" type="button" 
onClick={(): void => onNewTip(i)}>
-                  New Tip
+                  <button class="button is-small is-info jb-modal" 
type="button" onClick={(): void => onNewTip(i)}>
+                    New Tip
                 </button>
-              </div>
-            </td>
-          </tr>
-        })}
+                </div>
+              </td>
+            </tr>
+          })}
 
-      </tbody>
-    </table></div>)
+        </tbody>
+      </table></div>)
 }
 
 function EmptyTable(): VNode {
@@ -186,34 +143,34 @@ function EmptyTable(): VNode {
   </div>
 }
 
-function TableWithoutFund({ rowSelection, rowSelectionHandler, instances, 
onNewTip, onSelect, onDelete }: TableProps): VNode {
+function TableWithoutFund({ instances, onSelect, onDelete }: TableProps): 
VNode {
   return (
     <div class="table-container">
-    <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
-      <thead>
-        <tr>
-          <th><Translate>Created at</Translate></th>
-          <th><Translate>Expires at</Translate></th>
-          <th><Translate>Expected Balance</Translate></th>
-          <th />
-        </tr>
-      </thead>
-      <tbody>
-        {instances.map(i => {
-          return <tr key={i.id}>
-            <td onClick={(): void => onSelect(i)} style={{cursor: 'pointer'}} 
>{i.creation_time.t_ms === "never" ? "never" : format(i.creation_time.t_ms, 
'yyyy/MM/dd HH:mm:ss')}</td>
-            <td onClick={(): void => onSelect(i)} style={{cursor: 'pointer'}} 
>{i.expiration_time.t_ms === "never" ? "never" : format(i.expiration_time.t_ms, 
'yyyy/MM/dd HH:mm:ss')}</td>
-            <td onClick={(): void => onSelect(i)} style={{cursor: 'pointer'}} 
>{i.merchant_initial_amount}</td>
-            <td class="is-actions-cell right-sticky">
-              <div class="buttons is-right">
-                <button class="button is-small is-danger jb-modal" 
type="button" onClick={(): void => onDelete(i)}>
-                  Delete
-                </button>
-              </div>
-            </td>
+      <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
+        <thead>
+          <tr>
+            <th><Translate>Created at</Translate></th>
+            <th><Translate>Expires at</Translate></th>
+            <th><Translate>Expected Balance</Translate></th>
+            <th />
           </tr>
-        })}
+        </thead>
+        <tbody>
+          {instances.map(i => {
+            return <tr key={i.id}>
+              <td onClick={(): void => onSelect(i)} style={{ cursor: 'pointer' 
}} >{i.creation_time.t_ms === "never" ? "never" : format(i.creation_time.t_ms, 
'yyyy/MM/dd HH:mm:ss')}</td>
+              <td onClick={(): void => onSelect(i)} style={{ cursor: 'pointer' 
}} >{i.expiration_time.t_ms === "never" ? "never" : 
format(i.expiration_time.t_ms, 'yyyy/MM/dd HH:mm:ss')}</td>
+              <td onClick={(): void => onSelect(i)} style={{ cursor: 'pointer' 
}} >{i.merchant_initial_amount}</td>
+              <td class="is-actions-cell right-sticky">
+                <div class="buttons is-right">
+                  <button class="button is-small is-danger jb-modal" 
type="button" onClick={(): void => onDelete(i)}>
+                    Delete
+                </button>
+                </div>
+              </td>
+            </tr>
+          })}
 
-      </tbody>
-    </table></div>)
+        </tbody>
+      </table></div>)
 }
diff --git a/packages/frontend/src/paths/instance/transfers/list/index.tsx 
b/packages/frontend/src/paths/instance/transfers/list/index.tsx
index d141011..5486451 100644
--- a/packages/frontend/src/paths/instance/transfers/list/index.tsx
+++ b/packages/frontend/src/paths/instance/transfers/list/index.tsx
@@ -28,6 +28,7 @@ import { Input } from '../../../../components/form/Input';
 import { InputBoolean } from '../../../../components/form/InputBoolean';
 import { InputSearchProduct } from 
'../../../../components/form/InputSearchProduct';
 import { InputSelector } from '../../../../components/form/InputSelector';
+import { MerchantBackend } from '../../../../declaration';
 import { HttpError } from '../../../../hooks/backend';
 import { useInstanceDetails } from '../../../../hooks/instance';
 import { useInstanceTransfers, useTransferAPI } from 
"../../../../hooks/transfer";
@@ -59,6 +60,17 @@ export default function ListTransfer({ onUnauthorized, 
onLoadError, onCreate, on
   const isNonVerifiedTransfers = form.verified === 'no' ? "is-active" : ''
   const isAllTransfers = form.verified === undefined ? 'is-active' : ''
 
+  const result = useInstanceTransfers({
+    position,
+    payto_uri: form.payto_uri === '' ? undefined : form.payto_uri,
+    verified: form.verified,
+  }, (id) => setPosition(id))
+
+  if (result.clientError && result.isUnauthorized) return onUnauthorized()
+  if (result.clientError && result.isNotfound) return onNotFound()
+  if (result.loading) return <Loading />
+  if (!result.ok) return onLoadError(result)
+
   return <section class="section is-main-section">
     <div class="columns">
       <div class="column" />
@@ -82,6 +94,9 @@ export default function ListTransfer({ onUnauthorized, 
onLoadError, onCreate, on
     </div>
     <View
       accounts={accounts}
+      transfers={result.data.transfers}
+      onLoadMoreBefore={result.isReachingStart ? result.loadMorePrev : 
undefined}
+      onLoadMoreAfter={result.isReachingEnd ? result.loadMore : undefined}
       form={form} onCreate={onCreate} onLoadError={onLoadError} 
onNotFound={onNotFound} onUnauthorized={onUnauthorized}
       position={position} setPosition={setPosition}
     />
@@ -89,31 +104,23 @@ export default function ListTransfer({ onUnauthorized, 
onLoadError, onCreate, on
 }
 
 interface ViewProps extends Props {
+  transfers: MerchantBackend.Transfers.TransferDetails[];
+  onLoadMoreBefore?: () => void;
+  onLoadMoreAfter?: () => void;
   position?: string;
   setPosition: (s: string) => void;
   form: Form;
   accounts: string[];
 }
 
-function View({ onUnauthorized, onLoadError, onCreate, onNotFound, position, 
form, setPosition, accounts }: ViewProps) {
-  const result = useInstanceTransfers({
-    position,
-    payto_uri: form.payto_uri === '' ? undefined : form.payto_uri,
-    verified: form.verified,
-  }, (id) => setPosition(id))
-
-  if (result.clientError && result.isUnauthorized) return onUnauthorized()
-  if (result.clientError && result.isNotfound) return onNotFound()
-  if (result.loading) return <Loading />
-  if (!result.ok) return onLoadError(result)
-
-  return <CardTable instances={result.data.transfers.map(o => ({ ...o, id: 
String(o.transfer_serial_id) }))}
+function View({ transfers, onCreate, accounts, onLoadMoreBefore, 
onLoadMoreAfter }: ViewProps) {
+  return <CardTable instances={transfers.map(o => ({ ...o, id: 
String(o.transfer_serial_id) }))}
     accounts={accounts}
     onCreate={onCreate}
     onDelete={() => null}
     onUpdate={() => null}
-    onLoadMoreBefore={result.loadMorePrev} 
hasMoreBefore={!result.isReachingStart}
-    onLoadMoreAfter={result.loadMore} hasMoreAfter={!result.isReachingEnd}
+    onLoadMoreBefore={onLoadMoreBefore} hasMoreBefore={!onLoadMoreBefore}
+    onLoadMoreAfter={onLoadMoreAfter} hasMoreAfter={!onLoadMoreAfter}
   />
 
 }
diff --git a/packages/frontend/src/paths/instance/update/UpdatePage.tsx 
b/packages/frontend/src/paths/instance/update/UpdatePage.tsx
index 9d6777f..e12e09f 100644
--- a/packages/frontend/src/paths/instance/update/UpdatePage.tsx
+++ b/packages/frontend/src/paths/instance/update/UpdatePage.tsx
@@ -31,6 +31,7 @@ import { InputGroup } from 
"../../../components/form/InputGroup";
 import { InputLocation } from "../../../components/form/InputLocation";
 import { InputPayto } from "../../../components/form/InputPayto";
 import { InputSecured } from "../../../components/form/InputSecured";
+import { UpdateTokenModal } from "../../../components/modal";
 import { useInstanceContext } from "../../../context/instance";
 import { MerchantBackend } from "../../../declaration";
 import { Translate, useTranslator } from "../../../i18n";
@@ -38,15 +39,16 @@ import { InstanceUpdateSchema as schema } from 
'../../../schemas';
 
 
 type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage & { 
auth_token?: string }
-
+//MerchantBackend.Instances.InstanceAuthConfigurationMessage
 interface Props {
-  onUpdate: (d: Entity, auth?: 
MerchantBackend.Instances.InstanceAuthConfigurationMessage) => void;
+  onUpdate: (d: Entity) => void;
+  onChangeAuth: (d: 
MerchantBackend.Instances.InstanceAuthConfigurationMessage) => Promise<void>;
   selected: MerchantBackend.Instances.QueryInstancesResponse;
   isLoading: boolean;
   onBack: () => void;
 }
 
-function convert(from: MerchantBackend.Instances.QueryInstancesResponse, 
token?: string): Entity {
+function convert(from: MerchantBackend.Instances.QueryInstancesResponse): 
Entity {
   const { accounts, ...rest } = from
   const payto_uris = accounts.filter(a => a.active).map(a => a.payto_uri)
   const defaults = {
@@ -54,7 +56,7 @@ function convert(from: 
MerchantBackend.Instances.QueryInstancesResponse, token?:
     default_pay_delay: { d_ms: 1000 * 60 * 60 }, //one hour
     default_wire_transfer_delay: { d_ms: 1000 * 60 * 60 * 2 }, //two hours
   }
-  return { ...defaults, ...rest, payto_uris, auth_token: from.auth.method === 
"external" ? undefined : token };
+  return { ...defaults, ...rest, payto_uris };
 }
 
 function getTokenValuePart(t?: string): string | undefined {
@@ -64,10 +66,24 @@ function getTokenValuePart(t?: string): string | undefined {
   return match[1]
 }
 
-export function UpdatePage({ onUpdate, selected, onBack }: Props): VNode {
-  const { token } = useInstanceContext()
+
+
+export function UpdatePage({ onUpdate, onChangeAuth, selected, onBack }: 
Props): VNode {
+  const { id, token } = useInstanceContext()
   const currentTokenValue = getTokenValuePart(token)
-  const [value, valueHandler] = useState<Partial<Entity>>(convert(selected, 
currentTokenValue))
+
+  function updateToken(token: string | undefined | null) {
+    const value = token && token.startsWith('secret-token:') ?
+      token.substring('secret-token:'.length) : token
+
+    if (!token) {
+      onChangeAuth({ method: 'external' })
+    } else {
+      onChangeAuth({ method: 'token', token: `secret-token:${value}` })
+    }
+  }
+
+  const [value, valueHandler] = useState<Partial<Entity>>(convert(selected))
 
   let errors: FormErrors<Entity> = {}
   try {
@@ -79,36 +95,72 @@ export function UpdatePage({ onUpdate, selected, onBack }: 
Props): VNode {
   const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== 
undefined)
   const submit = async (): Promise<void> => {
     // use conversion instead of this
-    const newToken = value.auth_token;
-    value.auth_token = undefined;
+    // const newToken = value.auth_token;
+    // value.auth_token = undefined;
 
     //if new token was not set or has been set to the actual current token
     //it is not needed to send a change
     //otherwise, checked where we are setting a new token or removing it
-    const auth: MerchantBackend.Instances.InstanceAuthConfigurationMessage | 
undefined =
-      newToken === undefined || newToken === currentTokenValue ? undefined : 
(newToken === null ?
-        { method: "external" } :
-        { method: "token", token: `secret-token:${newToken}` });
+    // const auth: MerchantBackend.Instances.InstanceAuthConfigurationMessage 
| undefined =
+    //   newToken === undefined || newToken === currentTokenValue ? undefined 
: (newToken === null ?
+    //     { method: "external" } :
+    //     { method: "token", token: `secret-token:${newToken}` });
 
     // remove above use conversion
     schema.validateSync(value, { abortEarly: false })
-    await onUpdate(schema.cast(value), auth);
+    await onUpdate(schema.cast(value));
     await onBack()
     return Promise.resolve()
   }
+  const [active, setActive] = useState(false);
 
   const i18n = useTranslator()
 
   return <div>
-    <section class="section is-main-section">
+    <section class="section ">
+
+      <section class="hero is-hero-bar">
+        <div class="hero-body">
+
+          <div class="level">
+            <div class="level-left">
+              <div class="level-item">
+                <span class="is-size-4">Instance id: <b>{id}</b></span>
+              </div>
+            </div>
+            <div class="level-right">
+              <div class="level-item">
+                <h1 class="title">
+                  <button class="button is-danger" onClick={(): void => { 
setActive(!active); }} >
+                    <div class="icon is-left"><i class="mdi mdi-lock-reset" 
/></div>
+                    <span><Translate>Manage token</Translate></span>
+                  </button>
+                </h1>
+              </div>
+            </div>
+          </div>
+        </div></section>
+
+      <div class="columns">
+        <div class="column" />
+        <div class="column is-four-fifths">
+          {active && <UpdateTokenModal oldToken={currentTokenValue}
+            onCancel={() => { setActive(false); }}
+            onClear={() => { updateToken(null); setActive(false); }}
+            onConfirm={(newToken) => {
+              updateToken(newToken); setActive(false)
+            }}
+          />}
+        </div>
+        <div class="column" />
+      </div>
+      <hr />
       <div class="columns">
         <div class="column" />
         <div class="column is-four-fifths">
           <FormProvider<Entity> errors={errors} object={value} 
valueHandler={valueHandler} >
 
-            <Input<Entity> name="name" label={i18n`Name`} tooltip={i18n`unique 
name of this instance`} />
-
-            <InputSecured<Entity> name="auth_token" label={i18n`Auth token`} />
+            <Input<Entity> name="name" label={i18n`Name`} 
tooltip={i18n`display name of this instance`} />
 
             <InputPayto<Entity> name="payto_uris" label={i18n`Account 
address`} help="x-taler-bank/bank.taler:5882/blogger" />
 
@@ -141,6 +193,6 @@ export function UpdatePage({ onUpdate, selected, onBack }: 
Props): VNode {
       </div>
     </section>
 
-  </div>
+  </div >
 
 }
diff --git a/packages/frontend/src/paths/instance/update/index.tsx 
b/packages/frontend/src/paths/instance/update/index.tsx
index 81c9a06..b226af0 100644
--- a/packages/frontend/src/paths/instance/update/index.tsx
+++ b/packages/frontend/src/paths/instance/update/index.tsx
@@ -15,6 +15,7 @@
  */
 import { Fragment, h, VNode } from "preact";
 import { Loading } from "../../../components/exception/loading";
+import { useInstanceContext } from "../../../context/instance";
 import { MerchantBackend } from "../../../declaration";
 import { HttpError } from "../../../hooks/backend";
 import { useInstanceAPI, useInstanceDetails } from "../../../hooks/instance";
@@ -32,8 +33,9 @@ export interface Props {
 }
 
 export default function Update({ onBack, onConfirm, onLoadError, onNotFound, 
onUpdateError, onUnauthorized }: Props): VNode {
-  const { updateInstance } = useInstanceAPI();
+  const { updateInstance, clearToken, setNewToken } = useInstanceAPI();
   const result = useInstanceDetails()
+  const { changeToken } = useInstanceContext()
 
   if (result.clientError && result.isUnauthorized) return onUnauthorized()
   if (result.clientError && result.isNotfound) return onNotFound()
@@ -45,8 +47,13 @@ export default function Update({ onBack, onConfirm, 
onLoadError, onNotFound, onU
       onBack={onBack}
       isLoading={false}
       selected={result.data}
-      onUpdate={(d: MerchantBackend.Instances.InstanceReconfigurationMessage, 
t?: MerchantBackend.Instances.InstanceAuthConfigurationMessage): Promise<void> 
=> {
-        return updateInstance(d, t).then(onConfirm).catch(onUpdateError)
-      }} />
+      onUpdate={(d: MerchantBackend.Instances.InstanceReconfigurationMessage): 
Promise<void> => {
+        return updateInstance(d).then(onConfirm).catch(onUpdateError)
+      }} 
+      onChangeAuth={(d: 
MerchantBackend.Instances.InstanceAuthConfigurationMessage): Promise<void> => {
+        const apiCall = d.method === 'external' ? clearToken() : 
setNewToken(d.token!);
+        return apiCall.then(() => 
changeToken(d.token)).then(onConfirm).catch(onUpdateError)
+      }} 
+      />
   </Fragment>
 }
\ No newline at end of file
diff --git a/packages/frontend/src/utils/amount.ts 
b/packages/frontend/src/utils/amount.ts
index c799d97..731dc76 100644
--- a/packages/frontend/src/utils/amount.ts
+++ b/packages/frontend/src/utils/amount.ts
@@ -13,6 +13,7 @@
  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 { Amounts } from "@gnu-taler/taler-util";
 import { MerchantBackend } from "../declaration";
 
 /**
@@ -55,24 +56,36 @@ export function mergeRefunds(prev: 
MerchantBackend.Orders.RefundDetails[], cur:
 }
 
 export const multiplyPrice = (price: string, q: number) => {
-  const [currency, value] = price.split(':')
-  const total = parseInt(value, 10) * q
-  return `${currency}:${total}`
+  const a = Amounts.parseOrThrow(price)
+  const r = Amounts.mult(a, q)
+  return Amounts.stringify(r.amount)
+  // const [currency, value] = price.split(':')
+  // const total = parseInt(value, 10) * q
+  // return `${currency}:${total}`
 }
 
 export const subtractPrices = (one: string, two: string) => {
-  const [currency, valueOne] = one.split(':')
-  const [, valueTwo] = two.split(':')
-  return `${currency}:${parseInt(valueOne, 10) - parseInt(valueTwo, 10)}`
+  const a = Amounts.parseOrThrow(one)
+  const b = Amounts.parseOrThrow(two)
+  const r = Amounts.sub(a, b)
+  return Amounts.stringify(r.amount)
+  // const [currency, valueOne] = one.split(':')
+  // const [, valueTwo] = two.split(':')
+  // return `${currency}:${parseInt(valueOne, 10) - parseInt(valueTwo, 10)}`
 }
 
-export const rate = (one?: string, two?: string) => {
-  const [, valueOne] = (one || '').split(':')
-  const [, valueTwo] = (two || '').split(':')
-  const intOne = parseInt(valueOne, 10)
-  const intTwo = parseInt(valueTwo, 10)
-  if (!intTwo) return intOne
-  if (!intOne) return 0
-  return intOne / intTwo
+export const rate = (one: string, two: string) => {
+  const a = Amounts.parseOrThrow(one)
+  const b = Amounts.parseOrThrow(two)
+  const af = Amounts.toFloat(a)
+  const bf = Amounts.toFloat(b)
+  return af / bf
+  // const [, valueOne] = (one || '').split(':')
+  // const [, valueTwo] = (two || '').split(':')
+  // const intOne = parseInt(valueOne, 10)
+  // const intTwo = parseInt(valueTwo, 10)
+  // if (!intTwo) return intOne
+  // if (!intOne) return 0
+  // return intOne / intTwo
 }
 
diff --git a/packages/frontend/src/utils/constants.ts 
b/packages/frontend/src/utils/constants.ts
index a8ab9ca..1f654c0 100644
--- a/packages/frontend/src/utils/constants.ts
+++ b/packages/frontend/src/utils/constants.ts
@@ -21,6 +21,7 @@
 
 //https://tools.ietf.org/html/rfc8905
 export const PAYTO_REGEX = 
/^payto:\/\/[a-zA-Z][a-zA-Z0-9-.]+(\/[a-zA-Z0-9\-\.\~\(\)@_%:!$&'*+,;=]*)*\??((amount|receiver-name|sender-name|instruction|message)=[a-zA-Z0-9\-\.\~\(\)@_%:!$'*+,;=]*&?)*$/
+export const PAYTO_WIRE_METHOD_LOOKUP = 
/payto:\/\/([a-zA-Z][a-zA-Z0-9-.]+)\/.*/
 
 export const AMOUNT_REGEX = /^[a-zA-Z][a-zA-Z]*:[0-9][0-9,]*\.?[0-9,]*$/
 

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