gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant-backoffice] branch master updated (a3fd3e8 -> f4cbd85)


From: gnunet
Subject: [taler-merchant-backoffice] branch master updated (a3fd3e8 -> f4cbd85)
Date: Mon, 09 Aug 2021 19:37:40 +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 a3fd3e8  add creditor name
     new e10811b  rename title
     new f4cbd85  payto uri form

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:
 .../frontend/src/components/form/InputGroup.tsx    |   9 +-
 .../src/components/form/InputPaytoForm.tsx         | 167 +++++++++++++++++++++
 .../instance/DefaultInstanceFormFields.tsx         |  17 +--
 .../frontend/src/paths/admin/create/CreatePage.tsx |  27 +---
 packages/frontend/src/paths/admin/list/View.tsx    |  24 +--
 .../src/paths/instance/update/UpdatePage.tsx       |  30 +---
 packages/frontend/src/scss/main.scss               |   1 +
 7 files changed, 199 insertions(+), 76 deletions(-)
 create mode 100644 packages/frontend/src/components/form/InputPaytoForm.tsx

diff --git a/packages/frontend/src/components/form/InputGroup.tsx 
b/packages/frontend/src/components/form/InputGroup.tsx
index a4252f0..8af9c7d 100644
--- a/packages/frontend/src/components/form/InputGroup.tsx
+++ b/packages/frontend/src/components/form/InputGroup.tsx
@@ -28,11 +28,12 @@ export interface Props<T> {
   label: ComponentChildren;
   tooltip?: ComponentChildren;
   alternative?: ComponentChildren;
+  fixed?: boolean;
   initialActive?: boolean;
 }
 
-export function InputGroup<T>({ name, label, children, tooltip, alternative, 
initialActive }: Props<keyof T>): VNode {
-  const [active, setActive] = useState(initialActive);
+export function InputGroup<T>({ name, label, children, tooltip, alternative, 
fixed, initialActive }: Props<keyof T>): VNode {
+  const [active, setActive] = useState(initialActive || fixed);
   const group = useGroupField<T>(name);
 
   return <div class="card">
@@ -46,13 +47,13 @@ export function InputGroup<T>({ name, label, children, 
tooltip, alternative, ini
           <i class="mdi mdi-alert" />
         </span>}
       </p>
-      <button class="card-header-icon" aria-label="more options" onClick={(): 
void => setActive(!active)}>
+      { !fixed && <button class="card-header-icon" aria-label="more options" 
onClick={(): void => setActive(!active)}>
         <span class="icon">
           {active ?
             <i class="mdi mdi-arrow-up" /> :
             <i class="mdi mdi-arrow-down" />}
         </span>
-      </button>
+      </button> }
     </header>
     {active ? <div class="card-content">
         {children}
diff --git a/packages/frontend/src/components/form/InputPaytoForm.tsx 
b/packages/frontend/src/components/form/InputPaytoForm.tsx
new file mode 100644
index 0000000..c52dc33
--- /dev/null
+++ b/packages/frontend/src/components/form/InputPaytoForm.tsx
@@ -0,0 +1,167 @@
+/*
+ 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, Fragment } from "preact";
+import { useCallback, useState } from "preact/hooks";
+import { Translate, useTranslator } from "../../i18n";
+import { FormErrors, FormProvider } from "./FormProvider";
+import { Input } from "./Input";
+import { InputGroup } from "./InputGroup";
+import { InputSelector } from "./InputSelector";
+import { InputProps, useField } from "./useField";
+
+export interface Props<T> extends InputProps<T> {
+  isValid?: (e: any) => boolean;
+}
+
+// https://datatracker.ietf.org/doc/html/rfc8905
+type Entity = {
+  target: string,
+  path: string,
+  path1: string,
+  path2: string,
+  host: string,
+  account: string,
+  options: {
+    'receiver-name'?: string,
+    sender?: string,
+    message?: string,
+    amount?: string,
+    instruction?: string,
+    [name: string]: string | undefined,
+  },
+}
+
+// const targets = ['ach', 'bic', 'iban', 'upi', 'bitcoin', 'ilp', 'void', 
'x-taler-bank']
+const targets = ['iban', 'x-taler-bank']
+const defaultTarget = { target: 'iban', options: {} }
+
+function undefinedIfEmpty<T>(obj: T): T | undefined {
+  return Object.keys(obj).some(k => (obj as any)[k] !== undefined) ? obj : 
undefined
+}
+
+export function InputPaytoForm<T>({ name, readonly, label, tooltip }: 
Props<keyof T>): VNode {
+  const { value: paytos, onChange, } = useField<T>(name);
+
+  const [value, valueHandler] = useState<Partial<Entity>>(defaultTarget)
+
+  if (value.path1) {
+    if (value.path2) {
+      value.path = `/${value.path1}/${value.path2}`
+    } else {
+      value.path = `/${value.path1}`
+    }
+  }
+  const i18n = useTranslator()
+
+  const url = new URL(`payto://${value.target}${value.path}`)
+  const ops = value.options!
+  Object.keys(ops).forEach(opt_key => {
+    const opt_value = ops[opt_key]
+    if (opt_value) url.searchParams.set(opt_key, opt_value)
+  })
+  const paytoURL = url.toString()
+
+  const errors: FormErrors<Entity> = {
+    target: !value.target ? i18n`required` : undefined,
+    path1: !value.path1 ? i18n`required` : (
+      value.target === 'iban' ? (
+        value.path1.length < 4 ? i18n`IBAN numbers usually have more that 4 
digits` : (
+          value.path1.length > 34 ? i18n`IBAN numbers usually have less that 
34 digits` :
+          undefined
+        )
+      ): undefined 
+    ),
+    path2: value.target === 'x-taler-bank' ? (!value.path2 ? i18n`required` : 
undefined) : undefined,
+    options: undefinedIfEmpty({
+      'receiver-name': !value.options?.["receiver-name"] ? i18n`required` : 
undefined,
+    })
+  }
+
+  const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== 
undefined)
+
+  const submit = useCallback((): void => {
+    const alreadyExists = paytos.findIndex((x:string) => x === paytoURL) !== 
-1;
+    if (!alreadyExists) {
+      onChange([paytoURL, ...paytos] as any)
+    }
+    valueHandler(defaultTarget)
+  }, [value])
+
+
+  //FIXME: translating plural singular
+  return (
+    <InputGroup name="payto" label={label} fixed tooltip={tooltip}>
+      <FormProvider<Entity> name="tax" errors={errors} object={value} 
valueHandler={valueHandler} >
+
+        <InputSelector<Entity> name="target" label={i18n`Target type`} 
tooltip={i18n`Method to use for wire transfer`} values={targets} />
+
+        {value.target === 'ach' && <Fragment>
+          <Input<Entity> name="path1" label={i18n`Routing`} 
tooltip={i18n`Routing number.`} />
+          <Input<Entity> name="path2" label={i18n`Account`} 
tooltip={i18n`Account number.`} />
+        </Fragment>}
+        {value.target === 'bic' && <Fragment>
+          <Input<Entity> name="path1" label={i18n`Code`} 
tooltip={i18n`Business Identifier Code.`} />
+        </Fragment>}
+        {value.target === 'iban' && <Fragment>
+          <Input<Entity> name="path1" label={i18n`Account`} tooltip={i18n`Bank 
Account Number.`} />
+        </Fragment>}
+        {value.target === 'upi' && <Fragment>
+          <Input<Entity> name="path1" label={i18n`Account`} 
tooltip={i18n`Unified Payment Interface.`} />
+        </Fragment>}
+        {value.target === 'bitcoin' && <Fragment>
+          <Input<Entity> name="path1" label={i18n`Address`} 
tooltip={i18n`Bitcoin protocol.`} />
+        </Fragment>}
+        {value.target === 'ilp' && <Fragment>
+          <Input<Entity> name="path1" label={i18n`Address`} 
tooltip={i18n`Interledger protocol.`} />
+        </Fragment>}
+        {value.target === 'void' && <Fragment>
+        </Fragment>}
+        {value.target === 'x-taler-bank' && <Fragment>
+          <Input<Entity> name="path1" label={i18n`Host`} tooltip={i18n`Bank 
host.`} />
+          <Input<Entity> name="path2" label={i18n`Account`} tooltip={i18n`Bank 
account.`} />
+        </Fragment>}
+
+        <Input name="options.receiver-name" label={i18n`Name`} 
tooltip={i18n`Bank account owner's name.`} />
+
+        <div class="field is-horizontal">
+          <div class="field-label is-normal" />
+          <div class="field-body" style={{ display: 'block' }}>
+            {paytos.map((v: any, i: number) => <div key={i} class="tags 
has-addons mt-3 mb-0 mr-3" style={{ flexWrap: 'nowrap' }}>
+              <span class="tag is-medium is-info mb-0" style={{ maxWidth: 
'90%' }}>{v}</span>
+              <a class="tag is-medium is-danger is-delete mb-0" onClick={() => 
{
+                onChange(paytos.filter((f: any) => f !== v) as any);
+              }} />
+            </div>
+            )}
+            {!paytos.length && i18n`No accounts yet.`}
+          </div>
+        </div>
+
+        <div class="buttons is-right mt-5">
+          <button class="button is-info"
+            data-tooltip={i18n`add tax to the tax list`}
+            disabled={hasErrors}
+            onClick={submit}><Translate>Add</Translate></button>
+        </div>
+      </FormProvider>
+    </InputGroup>
+  )
+}
diff --git 
a/packages/frontend/src/components/instance/DefaultInstanceFormFields.tsx 
b/packages/frontend/src/components/instance/DefaultInstanceFormFields.tsx
index 873ee80..fae8a35 100644
--- a/packages/frontend/src/components/instance/DefaultInstanceFormFields.tsx
+++ b/packages/frontend/src/components/instance/DefaultInstanceFormFields.tsx
@@ -20,16 +20,16 @@
 */
 
 import { Fragment, h } from "preact";
+import { useBackendContext } from "../../context/backend";
+import { useTranslator } from "../../i18n";
+import { Entity } from "../../paths/admin/create/CreatePage";
 import { Input } from "../form/Input";
 import { InputCurrency } from "../form/InputCurrency";
 import { InputDuration } from "../form/InputDuration";
 import { InputGroup } from "../form/InputGroup";
 import { InputLocation } from "../form/InputLocation";
-import { InputPayto } from "../form/InputPayto";
+import { InputPaytoForm } from "../form/InputPaytoForm";
 import { InputWithAddon } from "../form/InputWithAddon";
-import { useBackendContext } from "../../context/backend";
-import { useTranslator } from "../../i18n";
-import { Entity } from "../../paths/admin/create/CreatePage";
 
 export function DefaultInstanceFormFields({ readonlyId, showId }: { 
readonlyId?: boolean; showId: boolean }) {
   const i18n = useTranslator();
@@ -45,13 +45,8 @@ export function DefaultInstanceFormFields({ readonlyId, 
showId }: { readonlyId?:
       label={i18n`Business name`}
       tooltip={i18n`Legal name of the business represented by this instance.`} 
/>
 
-    <Input<Entity> name="creditor_name"
-      label={i18n`Creditor Name`}
-      tooltip={i18n`name of who receive the money`}
-    />
-
-    <InputPayto<Entity> name="payto_uris_base"
-      label={i18n`Bank account URI`} 
help="x-taler-bank/bank.taler:5882/blogger"
+    <InputPaytoForm<Entity> name="payto_uris"
+      label={i18n`Bank account`} 
       tooltip={i18n`URI specifying bank account for crediting revenue.`} />
 
     <InputCurrency<Entity> name="default_max_deposit_fee"
diff --git a/packages/frontend/src/paths/admin/create/CreatePage.tsx 
b/packages/frontend/src/paths/admin/create/CreatePage.tsx
index 76f02e1..f5fa7c9 100644
--- a/packages/frontend/src/paths/admin/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/admin/create/CreatePage.tsx
@@ -32,8 +32,6 @@ import { INSTANCE_ID_REGEX, PAYTO_REGEX } from 
"../../../utils/constants";
 import { Amounts } from "@gnu-taler/taler-util";
 
 export type Entity = MerchantBackend.Instances.InstanceConfigurationMessage & 
{ 
-  payto_uris_base: string[], // field to construct final payto URI
-  creditor_name: string, // name of the receiver for the payto URI
   auth_token?: string 
 }
 
@@ -47,6 +45,7 @@ interface Props {
 function with_defaults(id?: string): Partial<Entity> {
   return {
     id,
+    payto_uris: [],
     default_pay_delay: { d_ms: 1000 * 60 * 60 }, // one hour
     default_wire_fee_amortization: 1,
     default_wire_transfer_delay: { d_ms: 1000 * 2 * 60 * 60 * 24 }, // one day
@@ -64,23 +63,12 @@ export function CreatePage({ onCreate, onBack, forceId }: 
Props): VNode {
 
   const i18n = useTranslator()
 
-  if (value.payto_uris && value.payto_uris.length > 0) {
-    const payto = new URL(value.payto_uris[0])
-    value.creditor_name = payto.searchParams.get('receiver-name') || undefined
-    value.payto_uris_base = value.payto_uris.map( p => {
-      const payto = new URL(p)
-      payto.searchParams.delete('receiver-name')
-      return payto.toString()
-    })
-  }
-
   const errors: FormErrors<Entity> = {
     id: !value.id ? i18n`required` : (!INSTANCE_ID_REGEX.test(value.id) ? 
i18n`is not valid` : undefined),
     name: !value.name ? i18n`required` : undefined,
-    creditor_name: !value.creditor_name ? i18n`required` : undefined,
-    payto_uris_base:
-      !value.payto_uris_base || !value.payto_uris_base.length ? i18n`required` 
: (
-        undefinedIfEmpty(value.payto_uris_base.map(p => {
+    payto_uris:
+      !value.payto_uris || !value.payto_uris.length ? i18n`required` : (
+        undefinedIfEmpty(value.payto_uris.map(p => {
           return !PAYTO_REGEX.test(p) ? i18n`is not valid` : undefined
         }))
       ),
@@ -126,13 +114,6 @@ export function CreatePage({ onCreate, onBack, forceId }: 
Props): VNode {
     if (!value.address) value.address = {}
     if (!value.jurisdiction) value.jurisdiction = {}
     // remove above use conversion
-    const receiverName = value.creditor_name!
-    value.payto_uris = value.payto_uris_base?.map(p => {
-      const payto = new URL(p)
-      payto.searchParams.set('receiver-name', receiverName)
-      return payto.toString()
-    }) || []
-    value.payto_uris_base = undefined
     // schema.validateSync(value, { abortEarly: false })
     return onCreate(value as Entity);
   }
diff --git a/packages/frontend/src/paths/admin/list/View.tsx 
b/packages/frontend/src/paths/admin/list/View.tsx
index 8febcff..a77a5a1 100644
--- a/packages/frontend/src/paths/admin/list/View.tsx
+++ b/packages/frontend/src/paths/admin/list/View.tsx
@@ -36,14 +36,14 @@ interface Props {
 }
 
 export function View({ instances, onCreate, onDelete, onPurge, onUpdate, 
setInstanceName, selected }: Props): VNode {
-  const [show, setShow] = useState<"enabled" | "disabled" | null>("enabled");
-  const showIsEnabled = show === 'enabled' ? "is-active" : ''
-  const showIsDisabled = show === 'disabled' ? "is-active" : ''
+  const [show, setShow] = useState<"active" | "deleted" | null>("active");
+  const showIsActive = show === 'active' ? "is-active" : ''
+  const showIsDeleted = show === 'deleted' ? "is-active" : ''
   const showAll = show === null ? "is-active" : ''
   const i18n = useTranslator()
 
-  const showingInstances = showIsDisabled ? 
-    instances.filter(i => i.deleted) : (showIsEnabled ?
+  const showingInstances = showIsDeleted ? 
+    instances.filter(i => i.deleted) : (showIsActive ?
       instances.filter(i => !i.deleted) :
       instances)
   
@@ -54,18 +54,18 @@ export function View({ instances, onCreate, onDelete, 
onPurge, onUpdate, setInst
         <div class="column is-two-thirds">
           <div class="tabs" style={{ overflow: 'inherit' }}>
             <ul>
-              <li class={showIsEnabled}>
-                <div class="has-tooltip-right" data-tooltip={i18n`only show 
enabled instances`}>
-                  <a onClick={() => 
setShow("enabled")}><Translate>Enable</Translate></a>
+              <li class={showIsActive}>
+                <div class="has-tooltip-right" data-tooltip={i18n`Only show 
active instances`}>
+                  <a onClick={() => 
setShow("active")}><Translate>Active</Translate></a>
                 </div>
               </li>
-              <li class={showIsDisabled}>
-                <div class="has-tooltip-right" data-tooltip={i18n`only show 
disabled instances`}>
-                  <a onClick={() => 
setShow("disabled")}><Translate>Disable</Translate></a>
+              <li class={showIsDeleted}>
+                <div class="has-tooltip-right" data-tooltip={i18n`Only show 
deleted instances`}>
+                  <a onClick={() => 
setShow("deleted")}><Translate>Deleted</Translate></a>
                 </div>
               </li>
               <li class={showAll}>
-                <div class="has-tooltip-right" data-tooltip={i18n`show all 
instances`}>
+                <div class="has-tooltip-right" data-tooltip={i18n`Show all 
instances`}>
                   <a onClick={() => 
setShow(null)}><Translate>All</Translate></a>
                 </div>
               </li>
diff --git a/packages/frontend/src/paths/instance/update/UpdatePage.tsx 
b/packages/frontend/src/paths/instance/update/UpdatePage.tsx
index 4a965b6..0fa96ed 100644
--- a/packages/frontend/src/paths/instance/update/UpdatePage.tsx
+++ b/packages/frontend/src/paths/instance/update/UpdatePage.tsx
@@ -35,8 +35,6 @@ import { Amounts } from "@gnu-taler/taler-util";
 
 
 type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage & { 
-  payto_uris_base: string[], // field to construct final payto URI
-  creditor_name: string, // name of the receiver for the payto URI
   auth_token?: string 
 }
 
@@ -56,19 +54,8 @@ function convert(from: 
MerchantBackend.Instances.QueryInstancesResponse): Entity
     default_wire_fee_amortization: 1,
     default_pay_delay: { d_ms: 1000 * 60 * 60 }, //one hour
     default_wire_transfer_delay: { d_ms: 1000 * 60 * 60 * 2 }, //two hours
-    creditor_name: '',
-    payto_uris_base: new Array<string>()
   }
-  if (payto_uris && payto_uris.length > 0) {
-    const payto = new URL(payto_uris[0])
-    defaults.creditor_name = payto.searchParams.get('receiver-name') || ''
-    defaults.payto_uris_base = payto_uris.map( p => {
-      const payto = new URL(p)
-      payto.searchParams.delete('receiver-name')
-      return payto.toString()
-    })
-  }
-  return { ...defaults, ...rest, payto_uris: [] };
+  return { ...defaults, ...rest, payto_uris };
 }
 
 function getTokenValuePart(t?: string): string | undefined {
@@ -103,10 +90,9 @@ export function UpdatePage({ onUpdate, onChangeAuth, 
selected, onBack }: Props):
 
   const errors: FormErrors<Entity> = {
     name: !value.name ? i18n`required` : undefined,
-    creditor_name: !value.creditor_name ? i18n`required` : undefined,
-    payto_uris_base:
-      !value.payto_uris_base || !value.payto_uris_base.length ? i18n`required` 
: (
-        undefinedIfEmpty(value.payto_uris_base.map(p => {
+    payto_uris:
+      !value.payto_uris || !value.payto_uris.length ? i18n`required` : (
+        undefinedIfEmpty(value.payto_uris.map(p => {
           return !PAYTO_REGEX.test(p) ? i18n`is not valid` : undefined
         }))
       ),
@@ -144,14 +130,6 @@ export function UpdatePage({ onUpdate, onChangeAuth, 
selected, onBack }: Props):
 
   const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== 
undefined)
   const submit = async (): Promise<void> => {
-    const receiverName = value.creditor_name!
-    value.payto_uris = value.payto_uris_base?.map(p => {
-      const payto = new URL(p)
-      payto.searchParams.set('receiver-name', receiverName)
-      return payto.toString()
-    }) || []
-    value.payto_uris_base = undefined
-
     await onUpdate(schema.cast(value));
     await onBack()
     return Promise.resolve()
diff --git a/packages/frontend/src/scss/main.scss 
b/packages/frontend/src/scss/main.scss
index f9ae0ef..b523566 100644
--- a/packages/frontend/src/scss/main.scss
+++ b/packages/frontend/src/scss/main.scss
@@ -170,6 +170,7 @@ input:read-only {
 
 .icon[data-tooltip]:before {
   transition: none;
+  z-index: 5;
 }
 
 span[data-tooltip] {

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