[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.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-merchant-backoffice] branch master updated: transfer list and notification,
gnunet <=