gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant-backoffice] branch master updated: transfer list and not


From: gnunet
Subject: [taler-merchant-backoffice] branch master updated: transfer list and notification
Date: Tue, 04 May 2021 20:16:18 +0200

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

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

The following commit(s) were added to refs/heads/master by this push:
     new 7fc5f0a  transfer list and notification
7fc5f0a is described below

commit 7fc5f0aa0b6e738521ee7339d1f0f0b20db9915d
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Tue May 4 15:15:28 2021 -0300

    transfer list and notification
---
 CHANGELOG.md                                       |   6 +-
 DESIGN.md                                          |  30 ++++++
 crock.ts                                           |  95 +++++++++++++++++++
 packages/frontend/src/InstanceRoutes.tsx           |   8 +-
 .../frontend/src/components/form/InputLocation.tsx |  43 +++++++++
 .../frontend/src/components/form/InputStock.tsx    |  19 +---
 packages/frontend/src/components/form/useField.tsx |   4 +-
 packages/frontend/src/hooks/transfer.ts            |   4 +-
 .../frontend/src/paths/admin/create/CreatePage.tsx |  33 +------
 .../paths/instance/orders/create/CreatePage.tsx    |  16 +---
 .../paths/instance/transfers/create/CreatePage.tsx | 105 +++++++++++++++++++++
 .../src/paths/instance/transfers/create/index.tsx  |  36 ++++++-
 .../src/paths/instance/transfers/list/Table.tsx    |  76 +++++++--------
 .../src/paths/instance/transfers/list/index.tsx    |  23 ++---
 .../src/paths/instance/update/UpdatePage.tsx       |  33 +------
 15 files changed, 373 insertions(+), 158 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 24d0aba..8f6a571 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,13 +5,10 @@ The format is based on [Keep a 
Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic 
Versioning](https://semver.org/spec/v2.0.0.html).
 
 ## [Future work]
- - gettext templates should be generated from the source code (#6791)
 
  - date format (error handling)
- - allow point separator for amounts
  - red color when input is invalid (onchange)
  - validate everything using onChange
- - feature: input as date format
 
  - replace Yup and type definition with a taler-library for the purpose (first 
wait Florian to refactor wallet core)
  - add more doc style comments 
@@ -21,12 +18,11 @@ and this project adheres to [Semantic 
Versioning](https://semver.org/spec/v2.0.0
  - 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
- - product detail: we could have some button that brings us to the detailed 
screen for the product
  - 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
- - check that there is no place where the taxes are suming up 
+ - check that there is no place where the taxes are summing up 
  - translation missing: yup (check for some other dynamic message)
 ## [Unreleased]
  - fixed bug when updating token and not admin
diff --git a/DESIGN.md b/DESIGN.md
index b245d72..43596f8 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -68,6 +68,36 @@ The core concepts are:
  * <Input /> an others: defines UI, create <input /> DOM controls and access 
the
    form with useField()
 
+To use it you will need a state somewhere with the object holding all the form
+information.
+
+```
+const [state, setState] = useState({ name: '', age: 11 })
+```
+
+Optionally an error object an be built with the error messages
+
+```
+const errors = {
+  field1: undefined,
+  field2: 'should be greater than 18',
+}
+```
+
+This 3 elements are use to setup the FormProvider
+
+```
+<FormProvider errors={errors} object={state} valueHandler={setState}>
+...inputs
+</FormProvider>
+```
+
+Inputs should handle UI rendering and use `useField(name)` to get:
+
+  * error: the error message if there is one
+  * value: the current value of the object
+  * onChange: function to update the current field
+
 
 # Custom Hooks
 
diff --git a/crock.ts b/crock.ts
new file mode 100644
index 0000000..2431eb6
--- /dev/null
+++ b/crock.ts
@@ -0,0 +1,95 @@
+function getValue(chr: string): number {
+  let a = chr;
+  switch (chr) {
+    case "O":
+    case "o":
+      a = "0;";
+      break;
+    case "i":
+    case "I":
+    case "l":
+    case "L":
+      a = "1";
+      break;
+    case "u":
+    case "U":
+      a = "V";
+  }
+
+  if (a >= "0" && a <= "9") {
+    return a.charCodeAt(0) - "0".charCodeAt(0);
+  }
+
+  if (a >= "a" && a <= "z") a = a.toUpperCase();
+  let dec = 0;
+  if (a >= "A" && a <= "Z") {
+    if ("I" < a) dec++;
+    if ("L" < a) dec++;
+    if ("O" < a) dec++;
+    if ("U" < a) dec++;
+    return a.charCodeAt(0) - "A".charCodeAt(0) + 10 - dec;
+  }
+  throw new Error('encoding');
+}
+
+const encTable = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
+export function encodeCrock(data: ArrayBuffer): string {
+  const dataBytes = new Uint8Array(data);
+  let sb = "";
+  const size = data.byteLength;
+  let bitBuf = 0;
+  let numBits = 0;
+  let pos = 0;
+  while (pos < size || numBits > 0) {
+    if (pos < size && numBits < 5) {
+      const d = dataBytes[pos++];
+      bitBuf = (bitBuf << 8) | d;
+      numBits += 8;
+    }
+    if (numBits < 5) {
+      // zero-padding
+      bitBuf = bitBuf << (5 - numBits);
+      numBits = 5;
+    }
+    const v = (bitBuf >>> (numBits - 5)) & 31;
+    sb += encTable[v];
+    numBits -= 5;
+  }
+  return sb;
+}
+
+export function decodeCrock(encoded: string): Uint8Array {
+  const size = encoded.length;
+  let bitpos = 0;
+  let bitbuf = 0;
+  let readPosition = 0;
+  const outLen = Math.floor((size * 5) / 8);
+  const out = new Uint8Array(outLen);
+  let outPos = 0;
+
+  while (readPosition < size || bitpos > 0) {
+    if (readPosition < size) {
+      const v = getValue(encoded[readPosition++]);
+      bitbuf = (bitbuf << 5) | v;
+      bitpos += 5;
+    }
+    while (bitpos >= 8) {
+      const d = (bitbuf >>> (bitpos - 8)) & 0xff;
+      out[outPos++] = d;
+      bitpos -= 8;
+    }
+    if (readPosition == size && bitpos > 0) {
+      bitbuf = (bitbuf << (8 - bitpos)) & 0xff;
+      bitpos = bitbuf == 0 ? 0 : 8;
+    }
+  }
+  return out;
+}
+
+const bin = decodeCrock("PV2D1G85528VDSZGED8KE98FEXT3PWQ99WV590TNVKJKM3GM3GRG")
+const base64String = Buffer
+  .from(String.fromCharCode.apply(null, bin))
+  .toString('base64');
+
+console.log(base64String)
+
diff --git a/packages/frontend/src/InstanceRoutes.tsx 
b/packages/frontend/src/InstanceRoutes.tsx
index c1090dc..a6267d7 100644
--- a/packages/frontend/src/InstanceRoutes.tsx
+++ b/packages/frontend/src/InstanceRoutes.tsx
@@ -39,6 +39,7 @@ import ProductCreatePage from 
'./paths/instance/products/create';
 import ProductListPage from './paths/instance/products/list';
 import ProductUpdatePage from './paths/instance/products/update';
 import TransferListPage from './paths/instance/transfers/list';
+import TransferCreatePage from './paths/instance/transfers/create';
 import InstanceUpdatePage, { Props as InstanceUpdatePageProps } from 
"./paths/instance/update";
 import LoginPage from './paths/login';
 import NotFoundPage from './paths/notfound';
@@ -62,7 +63,7 @@ export enum InstancePaths {
   // tips_new = '/tip/new',
 
   transfers_list = '/transfers',
-  // transfers_new = '/transfer/new',
+  transfers_new = '/transfer/new',
 }
 
 // eslint-disable-next-line @typescript-eslint/no-empty-function
@@ -245,8 +246,13 @@ export function InstanceRoutes({ id, admin }: Props): 
VNode {
         onUnauthorized={LoginPageAccessDenied}
         onLoadError={ServerErrorRedirectTo(InstancePaths.update)}
         onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
+        onCreate={() => { route(InstancePaths.transfers_new) }}
       />
 
+      <Route path={InstancePaths.transfers_new} component={TransferCreatePage}
+        onConfirm={() => { route(InstancePaths.transfers_list) }}
+        onBack={() => { route(InstancePaths.transfers_list) }}
+      />
       {/**
        * Example pages
        */}
diff --git a/packages/frontend/src/components/form/InputLocation.tsx 
b/packages/frontend/src/components/form/InputLocation.tsx
new file mode 100644
index 0000000..12755f4
--- /dev/null
+++ b/packages/frontend/src/components/form/InputLocation.tsx
@@ -0,0 +1,43 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
+import { Fragment, h } from "preact";
+import { useTranslator } from "../../i18n";
+import { Input } from "./Input";
+
+export function InputLocation({name}:{name:string}) {
+  const i18n = useTranslator()
+  return <>
+    <Input name={`${name}.country`} label={i18n`Country`} />
+    <Input name={`${name}.address_lines`} inputType="multiline"
+      label={i18n`Address`}
+      toStr={(v: string[] | undefined) => !v ? '' : v.join('\n')}
+      fromStr={(v: string) => v.split('\n')}
+    />
+    <Input name={`${name}.building_number`} label={i18n`Building number`} />
+    <Input name={`${name}.building_name`} label={i18n`Building name`} />
+    <Input name={`${name}.street`} label={i18n`Street`} />
+    <Input name={`${name}.post_code`} label={i18n`Post code`} />
+    <Input name={`${name}.town_location`} label={i18n`Town location`} />
+    <Input name={`${name}.town`} label={i18n`Town`} />
+    <Input name={`${name}.district`} label={i18n`District`} />
+    <Input name={`${name}.country_subdivision`} label={i18n`Country 
subdivision`} />
+  </>
+}
\ No newline at end of file
diff --git a/packages/frontend/src/components/form/InputStock.tsx 
b/packages/frontend/src/components/form/InputStock.tsx
index edc381f..ce3add8 100644
--- a/packages/frontend/src/components/form/InputStock.tsx
+++ b/packages/frontend/src/components/form/InputStock.tsx
@@ -28,6 +28,7 @@ import { InputGroup } from "./InputGroup";
 import { InputNumber } from "./InputNumber";
 import { InputDate } from "./InputDate";
 import { Translate, useTranslator } from "../../i18n";
+import { InputLocation } from "./InputLocation";
 
 export interface Props<T> extends InputProps<T> {
   alreadyExist?: boolean;
@@ -149,23 +150,7 @@ export function InputStock<T>({ name, readonly, 
placeholder, tooltip, label, hel
           <InputDate<Entity> name="nextRestock" label={i18n`Next restock`} 
withTimestampSupport />
 
           <InputGroup<Entity> name="address" label={i18n`Delivery address`}>
-
-            <Input name="address.country" label={i18n`Country`} />
-
-            <Input name="address.address_lines" inputType="multiline"
-              label={i18n`Address`}
-              toStr={(v: string[] | undefined) => !v ? '' : v.join('\n')}
-              fromStr={(v: string) => v.split('\n')}
-            />
-
-            <Input name="address.building_number" label={i18n`Building 
number`} />
-            <Input name="address.building_name" label={i18n`Building name`} />
-            <Input name="address.street" label={i18n`Street`} />
-            <Input name="address.post_code" label={i18n`Post code`} />
-            <Input name="address.town_location" label={i18n`Town location`} />
-            <Input name="address.town" label={i18n`Town`} />
-            <Input name="address.district" label={i18n`District`} />
-            <Input name="address.country_subdivision" label={i18n`Country 
subdivision`} />
+            <InputLocation name="address" />
           </InputGroup>
         </FormProvider>
       </div>
diff --git a/packages/frontend/src/components/form/useField.tsx 
b/packages/frontend/src/components/form/useField.tsx
index b24635a..6b64e23 100644
--- a/packages/frontend/src/components/form/useField.tsx
+++ b/packages/frontend/src/components/form/useField.tsx
@@ -19,7 +19,7 @@
 * @author Sebastian Javier Marchano (sebasjm)
 */
 
-import { VNode } from "preact";
+import { ComponentChildren, VNode } from "preact";
 import { useFormContext } from "./FormProvider";
 
 interface Use<V> {
@@ -79,5 +79,5 @@ export interface InputProps<T> {
   placeholder?: string;
   tooltip?: string;
   readonly?: boolean;
-  help?:VNode;
+  help?: ComponentChildren;
 }
\ No newline at end of file
diff --git a/packages/frontend/src/hooks/transfer.ts 
b/packages/frontend/src/hooks/transfer.ts
index d840a82..3440678 100644
--- a/packages/frontend/src/hooks/transfer.ts
+++ b/packages/frontend/src/hooks/transfer.ts
@@ -20,10 +20,10 @@ import useSWR from 'swr';
 import { useInstanceContext } from '../context/instance';
 
 async function transferFetcher<T>(url: string, token: string, backend: 
string): Promise<HttpResponseOk<T>> {
-  return request<T>(`${backend}${url}`, { token, params: { payto_uri: '' } })
+  return request<T>(`${backend}${url}`, { token, params: {  } })
 }
 
-export function useTransferMutateAPI(): TransferMutateAPI {
+export function useTransferAPI(): TransferMutateAPI {
   const { url: baseUrl, token: adminToken } = useBackendContext();
   const { token: instanceToken, id, admin } = useInstanceContext();
 
diff --git a/packages/frontend/src/paths/admin/create/CreatePage.tsx 
b/packages/frontend/src/paths/admin/create/CreatePage.tsx
index 58a294e..2c479ca 100644
--- a/packages/frontend/src/paths/admin/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/admin/create/CreatePage.tsx
@@ -27,6 +27,7 @@ import { Input } from "../../../components/form/Input";
 import { InputCurrency } from "../../../components/form/InputCurrency";
 import { InputDuration } from "../../../components/form/InputDuration";
 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 { InputWithAddon } from "../../../components/form/InputWithAddon";
@@ -87,7 +88,7 @@ export function CreatePage({ onCreate, onBack, forceId }: 
Props): VNode {
 
             <InputSecured<Entity> name="auth_token" label={i18n`Auth token`} />
 
-            <InputPayto<Entity> name="payto_uris" label={i18n`Account 
address`} />
+            <InputPayto<Entity> name="payto_uris" label={i18n`Account 
address`} help="payto://x-taler-bank/bank.taler:5882/blogger" />
 
             <InputCurrency<Entity> name="default_max_deposit_fee" 
label={i18n`Default max deposit fee`} />
 
@@ -96,37 +97,11 @@ export function CreatePage({ onCreate, onBack, forceId }: 
Props): VNode {
             <Input<Entity> name="default_wire_fee_amortization" 
label={i18n`Default wire fee amortization`} />
 
             <InputGroup name="address" label={i18n`Address`}>
-              <Input name="address.country" label={i18n`Country`} />
-              <Input name="address.address_lines" inputType="multiline"
-                label={i18n`Address`}
-                toStr={(v: string[] | undefined) => !v ? '' : v.join('\n')}
-                fromStr={(v: string) => v.split('\n')}
-              />
-              <Input name="address.building_number" label={i18n`Building 
number`} />
-              <Input name="address.building_name" label={i18n`Building name`} 
/>
-              <Input name="address.street" label={i18n`Street`} />
-              <Input name="address.post_code" label={i18n`Post code`} />
-              <Input name="address.town_location" label={i18n`Town location`} 
/>
-              <Input name="address.town" label={i18n`Town`} />
-              <Input name="address.district" label={i18n`District`} />
-              <Input name="address.country_subdivision" label={i18n`Country 
subdivision`} />
+              <InputLocation name="address" />
             </InputGroup>
 
             <InputGroup name="jurisdiction" label={i18n`Jurisdiction`}>
-              <Input name="jurisdiction.country" label={i18n`Country`} />
-              <Input name="jurisdiction.address_lines" inputType="multiline"
-                label={i18n`Address`}
-                toStr={(v: string[] | undefined) => !v ? '' : v.join('\n')}
-                fromStr={(v: string) => v.split('\n')}
-              />
-              <Input name="jurisdiction.building_number" label={i18n`Building 
number`} />
-              <Input name="jurisdiction.building_name" label={i18n`Building 
name`} />
-              <Input name="jurisdiction.street" label={i18n`Street`} />
-              <Input name="jurisdiction.post_code" label={i18n`Post code`} />
-              <Input name="jurisdiction.town_location" label={i18n`Town 
location`} />
-              <Input name="jurisdiction.town" label={i18n`Town`} />
-              <Input name="jurisdiction.district" label={i18n`District`} />
-              <Input name="jurisdiction.country_subdivision" 
label={i18n`Country subdivision`} />
+              <InputLocation name="jurisdiction" />
             </InputGroup>
 
             <InputDuration<Entity> name="default_pay_delay" 
label={i18n`Default pay delay`} />
diff --git a/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx 
b/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
index a8bdd18..3308c1c 100644
--- a/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
@@ -28,6 +28,7 @@ import { Input } from "../../../../components/form/Input";
 import { InputCurrency } from "../../../../components/form/InputCurrency";
 import { InputDate } from "../../../../components/form/InputDate";
 import { InputGroup } from "../../../../components/form/InputGroup";
+import { InputLocation } from "../../../../components/form/InputLocation";
 import { ProductList } from "../../../../components/product/ProductList";
 import { useConfigContext } from "../../../../context/config";
 import { MerchantBackend, WithId } from "../../../../declaration";
@@ -305,20 +306,7 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
 
               <InputDate name="payments.delivery_date" label={i18n`Delivery 
date`} />
               {value.payments.delivery_date && <InputGroup 
name="payments.delivery_location" label={i18n`Location`} >
-                <Input name="payments.delivery_location.country" 
label={i18n`Country`} />
-                <Input name="payments.delivery_location.address_lines" 
inputType="multiline"
-                  label={i18n`Address`}
-                  toStr={(v: string[] | undefined) => !v ? '' : v.join('\n')}
-                  fromStr={(v: string) => v.split('\n')}
-                />
-                <Input name="payments.delivery_location.building_number" 
label={i18n`Building number`} />
-                <Input name="payments.delivery_location.building_name" 
label={i18n`Building name`} />
-                <Input name="payments.delivery_location.street" 
label={i18n`Street`} />
-                <Input name="payments.delivery_location.post_code" 
label={i18n`Post code`} />
-                <Input name="payments.delivery_location.town_location" 
label={i18n`Town location`} />
-                <Input name="payments.delivery_location.town" 
label={i18n`Town`} />
-                <Input name="payments.delivery_location.district" 
label={i18n`District`} />
-                <Input name="payments.delivery_location.country_subdivision" 
label={i18n`Country subdivision`} />
+                <InputLocation name="payments.delivery_location" />
               </InputGroup>}
 
               <InputCurrency name="payments.max_fee" label={i18n`Max fee`} />
diff --git 
a/packages/frontend/src/paths/instance/transfers/create/CreatePage.tsx 
b/packages/frontend/src/paths/instance/transfers/create/CreatePage.tsx
new file mode 100644
index 0000000..2199082
--- /dev/null
+++ b/packages/frontend/src/paths/instance/transfers/create/CreatePage.tsx
@@ -0,0 +1,105 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
+
+import { h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { FormErrors, FormProvider } from 
"../../../../components/form/FormProvider";
+import { Input } from "../../../../components/form/Input";
+import { InputCurrency } from "../../../../components/form/InputCurrency";
+import { InputWithAddon } from "../../../../components/form/InputWithAddon";
+import { MerchantBackend } from "../../../../declaration";
+import { Translate, useTranslator } from "../../../../i18n";
+
+type Entity = MerchantBackend.Transfers.TransferInformation
+
+interface Props {
+  onCreate: (d: Entity) => void;
+  onBack?: () => void;
+}
+
+// # The encoded symbol space does not include I, L, O or U
+// symbols = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'
+// # These five symbols are exclusively for checksum values
+// check_symbols = '*~$=U'
+
+
+const CROCKFORD_BASE32_REGEX = /^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]+[*~$=U]*$/
+const URL_REGEX = 
/^((https?:)(\/\/\/?)([\w]*(?::[\w]*)?@)?([\d\w\.-]+)(?::(\d+))?)\/$/
+
+export function CreatePage({ onCreate, onBack }: Props): VNode {
+  const i18n = useTranslator()
+
+  const [state, setState] = useState<Partial<Entity>>({
+    wtid: 'DCMGEM7F0DPW930M06C2AVNC6CFXT6HBQ2YVQH7EC8ZQ0W8SS9TG',
+    payto_uri: 'payto://x-taler-bank/bank.taler:5882/blogger',
+    exchange_url: 'http://exchange.taler:8081/',
+    credit_amount: 'COL:22.80'
+  })
+
+  const errors: FormErrors<Entity> = {
+    wtid: !state.wtid ? i18n`cannot be empty` :
+      (!CROCKFORD_BASE32_REGEX.test(state.wtid) ? i18n`check the id, doest 
look valid` :
+        (state.wtid.length !== 52 ? i18n`should have 52 characters, current 
${state.wtid.length}` :
+          undefined)),
+    payto_uri: !state.payto_uri ? i18n`cannot be empty` : undefined,
+    credit_amount: !state.credit_amount ? i18n`cannot be empty` : undefined,
+    exchange_url: !state.exchange_url ? i18n`cannot be empty` :
+      (!URL_REGEX.test(state.exchange_url) ? i18n`URL doesn't have the right 
format` : undefined),
+  }
+
+  const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== 
undefined)
+
+  const submitForm = () => {
+    if (hasErrors) return
+    onCreate({ ...state, payto_uri: 
'payto://x-taler-bank/bank.taler:5882/blogger' } as any)
+  }
+
+  return <div>
+    <section class="section is-main-section">
+      <div class="columns">
+        <div class="column" />
+        <div class="column is-two-thirds">
+
+          <FormProvider object={state} valueHandler={setState} errors={errors}>
+            <Input<Entity> name="wtid" label={i18n`Transfer ID`} help="" />
+            <InputWithAddon<Entity> name="payto_uri"
+              label={i18n`Account Address`}
+              addonBefore="payto://"
+              toStr={(v?: string) => v ? v.substring("payto://".length) : ''}
+              fromStr={(v: string) => !v ? '' : `payto://${v}`}
+              help="x-taler-bank/bank.taler:5882/blogger" />
+            <Input<Entity> name="exchange_url"
+              label={i18n`Exchange URL`}
+              help="http://exchange.taler:8081/"; />
+            <InputCurrency<Entity> name="credit_amount" label={i18n`Amount`} />
+          </FormProvider>
+
+          <div class="buttons is-right mt-5">
+            {onBack && <button class="button" onClick={onBack} 
><Translate>Cancel</Translate></button>}
+            <button class="button is-success" disabled={hasErrors} 
onClick={submitForm} ><Translate>Confirm</Translate></button>
+          </div>
+
+        </div>
+        <div class="column" />
+      </div>
+    </section>
+  </div>
+}
\ No newline at end of file
diff --git a/packages/frontend/src/paths/instance/transfers/create/index.tsx 
b/packages/frontend/src/paths/instance/transfers/create/index.tsx
index 7c9f95f..e2aec58 100644
--- a/packages/frontend/src/paths/instance/transfers/create/index.tsx
+++ b/packages/frontend/src/paths/instance/transfers/create/index.tsx
@@ -19,8 +19,38 @@
 * @author Sebastian Javier Marchano (sebasjm)
 */
 
-import { h, VNode } from 'preact';
+import { Fragment, h, VNode } from 'preact';
+import { useState } from 'preact/hooks';
+import { NotificationCard } from '../../../../components/menu';
+import { MerchantBackend } from '../../../../declaration';
+import { useTransferAPI } from '../../../../hooks/transfer';
+import { useTranslator } from '../../../../i18n';
+import { Notification } from '../../../../utils/types';
+import { CreatePage } from './CreatePage';
 
-export default function CreateTransfer():VNode {
-  return <div>transfer create page</div>
+export type Entity = MerchantBackend.Transfers.TransferInformation
+interface Props {
+  onBack?: () => void;
+  onConfirm: () => void;
+}
+
+export default function CreateTransfer({onConfirm, onBack}:Props): VNode {
+  const { informTransfer } = useTransferAPI()
+  const [notif, setNotif] = useState<Notification | undefined>(undefined)
+  const i18n = useTranslator()
+
+  return <>
+    <NotificationCard notification={notif} />
+    <CreatePage
+      onBack={onBack}
+      onCreate={(request: MerchantBackend.Transfers.TransferInformation) => {
+        informTransfer(request).then(() => onConfirm()).catch((error) => {
+          setNotif({
+            message: i18n`could not inform transfer`,
+            type: "ERROR",
+            description: error.message
+          })
+        })
+      }} />
+  </>
 }
\ No newline at end of file
diff --git a/packages/frontend/src/paths/instance/transfers/list/Table.tsx 
b/packages/frontend/src/paths/instance/transfers/list/Table.tsx
index 52fbb4d..2f64d8a 100644
--- a/packages/frontend/src/paths/instance/transfers/list/Table.tsx
+++ b/packages/frontend/src/paths/instance/transfers/list/Table.tsx
@@ -14,15 +14,16 @@
  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 { h, VNode } from "preact"
 import { StateUpdater, useEffect, useState } from "preact/hooks"
 import { MerchantBackend, WithId } from "../../../../declaration"
-import { Translate } from "../../../../i18n"
+import { Translate, useTranslator } from "../../../../i18n"
 import { Actions, buildActions } from "../../../../utils/table"
 
 type Entity = MerchantBackend.Transfers.TransferDetails & WithId
@@ -32,6 +33,7 @@ interface Props {
   onUpdate: (id: string) => void;
   onDelete: (id: Entity) => void;
   onCreate: () => void;
+  accounts: string[];
   selected?: boolean;
 }
 
@@ -62,7 +64,7 @@ export function CardTable({ instances, onCreate, onUpdate, 
onDelete, selected }:
 
         <button class={rowSelection.length > 0 ? "button is-danger" : 
"is-hidden"}
           type="button" onClick={(): void => 
actionQueueHandler(buildActions(instances, rowSelection, 'DELETE'))} >
-          Delete
+          <Translate>Delete</Translate>
         </button>
       </div>
       <div class="card-header-icon" aria-label="more options">
@@ -96,46 +98,34 @@ 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, onUpdate, 
onDelete }: TableProps): VNode {
+function Table({ instances }: TableProps): VNode {
+  const i18n = useTranslator()
   return (
     <div class="table-container">
-    <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
-      <thead>
-        <tr>
-          <th class="is-checkbox-cell">
-            <label class="b-checkbox checkbox">
-              <input type="checkbox" checked={rowSelection.length === 
instances.length} onClick={(): void => rowSelectionHandler(rowSelection.length 
=== instances.length ? [] : instances.map(i => i.id))} />
-              <span class="check" />
-            </label>
-          </th>
-          <th><Translate>ID</Translate></th>
-          <th><Translate>Name</Translate></th>
-          <th />
-        </tr>
-      </thead>
-      <tbody>
-        {instances.map(i => {
-          return <tr key={i.id}>
-            <td class="is-checkbox-cell">
-              <label class="b-checkbox checkbox">
-                <input type="checkbox" checked={rowSelection.indexOf(i.id) != 
-1} onClick={(): void => rowSelectionHandler(toggleSelected(i.id))} />
-                <span class="check" />
-              </label>
-            </td>
-            <td onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.credit_amount}</td>
-            <td onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.exchange_url}</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>Credit</Translate></th>
+            <th><Translate>Address</Translate></th>
+            <th><Translate>Exchange URL</Translate></th>
+            <th><Translate>Confirmed</Translate></th>
+            <th><Translate>Verified</Translate></th>
+            <th><Translate>Executed at</Translate></th>
           </tr>
-        })}
-
-      </tbody>
-    </table>
+        </thead>
+        <tbody>
+          {instances.map(i => {
+            return <tr key={i.id}>
+              <td>{i.credit_amount}</td>
+              <td>{i.payto_uri}</td>
+              <td>{i.exchange_url}</td>
+              <td>{i.confirmed ? i18n`yes` : i18n`no`}</td>
+              <td>{i.verified ? i18n`yes` : i18n`no`}</td>
+              <td>{i.execution_time ? (i.execution_time.t_ms == 'never' ? 
i18n`never` : format(i.execution_time.t_ms, 'yyyy/MM/dd HH:mm:ss')) : 
i18n`unknown`}</td>
+            </tr>
+          })}
+        </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 7935a3a..787089b 100644
--- a/packages/frontend/src/paths/instance/transfers/list/index.tsx
+++ b/packages/frontend/src/paths/instance/transfers/list/index.tsx
@@ -22,34 +22,31 @@
 import { h, VNode } from 'preact';
 import { Loading } from '../../../../components/exception/loading';
 import { HttpError } from '../../../../hooks/backend';
-import { useInstanceTransfers, useTransferMutateAPI } from 
"../../../../hooks/transfer";
+import { useInstanceDetails } from '../../../../hooks/instance';
+import { useInstanceTransfers, useTransferAPI } from 
"../../../../hooks/transfer";
 import { CardTable } from './Table';
 
 interface Props {
   onUnauthorized: () => VNode;
   onLoadError: (error: HttpError) => VNode;
   onNotFound: () => VNode;
+  onCreate: () => void;
 }
-export default function ListTransfer({ onUnauthorized, onLoadError, onNotFound 
}: Props): VNode {
+export default function ListTransfer({ onUnauthorized, onLoadError, onCreate, 
onNotFound }: Props): VNode {
   const result = useInstanceTransfers()
-  const { informTransfer } = useTransferMutateAPI()
-  
+  const instance = useInstanceDetails()
+
   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)
 
+  const accounts = !instance.ok? [] : instance.data.accounts.map(a => 
a.payto_uri)
+
   return <section class="section is-main-section">
     <CardTable instances={result.data.transfers.map(o => ({ ...o, id: 
String(o.transfer_serial_id) }))}
-      onCreate={() => informTransfer({
-        wtid: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
-
-        // exchange: payto://x-taler-bank/bank.taler:5882/exchangeminator
-        // payto://x-taler-bank/bank.taler:5882/9?subject=qwe&amount=COL:10
-        payto_uri: 'payto://x-taler-bank/bank.taler:5882/blogger',
-        exchange_url: 'http://exchange.taler:8081/',
-        credit_amount: 'COL:2'
-      })}
+      accounts={accounts}
+      onCreate={onCreate}
       onDelete={() => null}
       onUpdate={() => null}
     />
diff --git a/packages/frontend/src/paths/instance/update/UpdatePage.tsx 
b/packages/frontend/src/paths/instance/update/UpdatePage.tsx
index 86432fa..c1ab5c6 100644
--- a/packages/frontend/src/paths/instance/update/UpdatePage.tsx
+++ b/packages/frontend/src/paths/instance/update/UpdatePage.tsx
@@ -27,6 +27,7 @@ import { Input } from "../../../components/form/Input";
 import { InputCurrency } from "../../../components/form/InputCurrency";
 import { InputDuration } from "../../../components/form/InputDuration";
 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 { useInstanceContext } from "../../../context/instance";
@@ -106,7 +107,7 @@ export function UpdatePage({ onUpdate, selected, onBack }: 
Props): VNode {
 
             <InputSecured<Entity> name="auth_token" label={i18n`Auth token`} />
 
-            <InputPayto<Entity> name="payto_uris" label={i18n`Account 
address`} />
+            <InputPayto<Entity> name="payto_uris" label={i18n`Account 
address`} help="payto://x-taler-bank/bank.taler:5882/blogger" />
 
             <InputCurrency<Entity> name="default_max_deposit_fee" 
label={i18n`Default max deposit fee`} />
 
@@ -115,37 +116,11 @@ export function UpdatePage({ onUpdate, selected, onBack 
}: Props): VNode {
             <Input<Entity> name="default_wire_fee_amortization" 
inputType="number" label={i18n`Default wire fee amortization`} />
 
             <InputGroup name="address" label={i18n`Address`}>
-              <Input name="address.country" label={i18n`Country`} />
-              <Input name="address.address_lines" inputType="multiline"
-                label={i18n`Address`}
-                toStr={(v: string[] | undefined) => !v ? '' : v.join('\n')}
-                fromStr={(v: string) => v.split('\n')}
-              />
-              <Input name="address.building_number" label={i18n`Building 
number`} />
-              <Input name="address.building_name" label={i18n`Building name`} 
/>
-              <Input name="address.street" label={i18n`Street`} />
-              <Input name="address.post_code" label={i18n`Post code`} />
-              <Input name="address.town_location" label={i18n`Town location`} 
/>
-              <Input name="address.town" label={i18n`Town`} />
-              <Input name="address.district" label={i18n`District`} />
-              <Input name="address.country_subdivision" label={i18n`Country 
subdivision`} />
+              <InputLocation name="address" />
             </InputGroup>
 
             <InputGroup name="jurisdiction" label={i18n`Jurisdiction`}>
-              <Input name="jurisdiction.country" label={i18n`Country`} />
-              <Input name="jurisdiction.address_lines" inputType="multiline"
-                label={i18n`Address`}
-                toStr={(v: string[] | undefined) => !v ? '' : v.join('\n')}
-                fromStr={(v: string) => v.split('\n')}
-              />
-              <Input name="jurisdiction.building_number" label={i18n`Building 
number`} />
-              <Input name="jurisdiction.building_name" label={i18n`Building 
name`} />
-              <Input name="jurisdiction.street" label={i18n`Street`} />
-              <Input name="jurisdiction.post_code" label={i18n`Post code`} />
-              <Input name="jurisdiction.town_location" label={i18n`Town 
location`} />
-              <Input name="jurisdiction.town" label={i18n`Town`} />
-              <Input name="jurisdiction.district" label={i18n`District`} />
-              <Input name="jurisdiction.country_subdivision" 
label={i18n`Country subdivision`} />
+              <InputLocation name="jurisdiction" />
             </InputGroup>
 
             <InputDuration<Entity> name="default_pay_delay" 
label={i18n`Default pay delay`} />

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