summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2021-06-10 23:51:14 -0300
committerSebastian <sebasjm@gmail.com>2021-06-10 23:55:04 -0300
commitc9c04a14be4bf9a70cd1730d2e8b5aa8bd38f032 (patch)
treec59f0b0906f97ffe2c8394cfb963c778b82ad244
parent352e23dbbc9a83b522964f3ba7fb06295d38c835 (diff)
downloadmerchant-backoffice-c9c04a14be4bf9a70cd1730d2e8b5aa8bd38f032.tar.gz
merchant-backoffice-c9c04a14be4bf9a70cd1730d2e8b5aa8bd38f032.tar.bz2
merchant-backoffice-c9c04a14be4bf9a70cd1730d2e8b5aa8bd38f032.zip
lots of fixes in same commit
added tooltips in buttons and fix tooltips styles in corner cases added a start for required fields token -> access token fixed query product list refactor instance update/create form some more comments from christian email
-rw-r--r--packages/frontend/src/components/exception/AsyncButton.tsx12
-rw-r--r--packages/frontend/src/components/exception/login.tsx6
-rw-r--r--packages/frontend/src/components/form/DatePicker.tsx5
-rw-r--r--packages/frontend/src/components/form/Input.tsx7
-rw-r--r--packages/frontend/src/components/form/InputArray.tsx13
-rw-r--r--packages/frontend/src/components/form/InputDate.tsx8
-rw-r--r--packages/frontend/src/components/form/InputGroup.tsx8
-rw-r--r--packages/frontend/src/components/form/InputSearchProduct.tsx24
-rw-r--r--packages/frontend/src/components/form/InputSecured.stories.tsx6
-rw-r--r--packages/frontend/src/components/form/InputSecured.tsx2
-rw-r--r--packages/frontend/src/components/form/InputStock.tsx27
-rw-r--r--packages/frontend/src/components/form/InputWithAddon.tsx7
-rw-r--r--packages/frontend/src/components/form/TextField.tsx53
-rw-r--r--packages/frontend/src/components/form/useField.tsx10
-rw-r--r--packages/frontend/src/components/instance/DefaultInstanceFormFields.tsx88
-rw-r--r--packages/frontend/src/components/menu/index.tsx3
-rw-r--r--packages/frontend/src/components/modal/index.tsx25
-rw-r--r--packages/frontend/src/context/backend.ts1
-rw-r--r--packages/frontend/src/declaration.d.ts9
-rw-r--r--packages/frontend/src/hooks/product.ts2
-rw-r--r--packages/frontend/src/i18n/index.tsx5
-rw-r--r--packages/frontend/src/paths/admin/create/CreatePage.tsx72
-rw-r--r--packages/frontend/src/paths/admin/create/InstanceCreatedSuccessfully.tsx2
-rw-r--r--packages/frontend/src/paths/admin/list/Table.tsx85
-rw-r--r--packages/frontend/src/paths/instance/orders/create/CreatePage.tsx2
-rw-r--r--packages/frontend/src/paths/instance/orders/create/InventoryProductForm.tsx8
-rw-r--r--packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx20
-rw-r--r--packages/frontend/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx9
-rw-r--r--packages/frontend/src/paths/instance/orders/details/DetailPage.tsx171
-rw-r--r--packages/frontend/src/paths/instance/orders/details/Timeline.tsx5
-rw-r--r--packages/frontend/src/paths/instance/orders/list/Table.tsx7
-rw-r--r--packages/frontend/src/paths/instance/orders/list/index.tsx51
-rw-r--r--packages/frontend/src/paths/instance/products/create/CreatePage.tsx2
-rw-r--r--packages/frontend/src/paths/instance/products/create/CreatedSuccessfully.tsx1
-rw-r--r--packages/frontend/src/paths/instance/products/list/Table.tsx82
-rw-r--r--packages/frontend/src/paths/instance/products/update/UpdatePage.tsx21
-rw-r--r--packages/frontend/src/paths/instance/reserves/create/CreatePage.tsx2
-rw-r--r--packages/frontend/src/paths/instance/reserves/details/DetailPage.tsx107
-rw-r--r--packages/frontend/src/paths/instance/reserves/list/Table.tsx12
-rw-r--r--packages/frontend/src/paths/instance/transfers/create/CreatePage.tsx2
-rw-r--r--packages/frontend/src/paths/instance/transfers/list/Table.tsx9
-rw-r--r--packages/frontend/src/paths/instance/transfers/list/index.tsx20
-rw-r--r--packages/frontend/src/paths/instance/update/UpdatePage.tsx34
-rw-r--r--packages/frontend/src/scss/main.scss71
44 files changed, 661 insertions, 455 deletions
diff --git a/packages/frontend/src/components/exception/AsyncButton.tsx b/packages/frontend/src/components/exception/AsyncButton.tsx
index 95e4e72..3dad3e0 100644
--- a/packages/frontend/src/components/exception/AsyncButton.tsx
+++ b/packages/frontend/src/components/exception/AsyncButton.tsx
@@ -25,12 +25,12 @@ import { useAsync } from "../../hooks/async";
import { Translate } from "../../i18n";
type Props = {
- children: ComponentChildren,
+ children: ComponentChildren,
disabled: boolean;
onClick?: () => Promise<void>;
};
-export function AsyncButton({ onClick, disabled, children }: Props) {
+export function AsyncButton({ onClick, disabled, children, ...rest }: Props) {
const { isSlow, isLoading, request, cancel } = useAsync(onClick);
if (isSlow) {
@@ -40,7 +40,9 @@ export function AsyncButton({ onClick, disabled, children }: Props) {
return <button class="button"><Translate>Loading...</Translate></button>;
}
- return <button class="button is-success" onClick={request} disabled={disabled}>
- {children}
- </button>;
+ return <span {...rest}>
+ <button class="button is-success" onClick={request} disabled={disabled}>
+ {children}
+ </button>
+ </span>;
}
diff --git a/packages/frontend/src/components/exception/login.tsx b/packages/frontend/src/components/exception/login.tsx
index e7c9802..fca81ae 100644
--- a/packages/frontend/src/components/exception/login.tsx
+++ b/packages/frontend/src/components/exception/login.tsx
@@ -55,7 +55,7 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode {
<p class="modal-card-title">{i18n`Login required`}</p>
</header>
<section class="modal-card-body" style={{ border: '1px solid', borderTop: 0, borderBottom: 0 }}>
- {i18n`Please enter your auth token.`}
+ <Translate>Please enter your access token.</Translate>
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">URL</label>
@@ -74,12 +74,12 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode {
</div>
<div class="field is-horizontal">
<div class="field-label is-normal">
- <label class="label">Token</label>
+ <label class="label"><Translate>Access Token</Translate></label>
</div>
<div class="field-body">
<div class="field">
<p class="control is-expanded">
- <input class="input" type="text" placeholder={"set new token"} name="token"
+ <input class="input" type="text" placeholder={"set new access token"} name="token"
onKeyPress={e => e.keyCode === 13 ? onConfirm(url, token ? `secret-token:${token}` : undefined) : null}
value={token}
onInput={(e): void => setToken(e?.currentTarget.value)}
diff --git a/packages/frontend/src/components/form/DatePicker.tsx b/packages/frontend/src/components/form/DatePicker.tsx
index 89d302b..084b7b0 100644
--- a/packages/frontend/src/components/form/DatePicker.tsx
+++ b/packages/frontend/src/components/form/DatePicker.tsx
@@ -261,11 +261,6 @@ export class DatePicker extends Component<Props, State> {
</div>}
- {/* {!selectYearMode && <div class="datePicker--actions">
- <button onClick={this.closeDatePicker}>CANCEL</button>
- <button onClick={this.passDateToParent}>OK</button>
- </div>} */}
-
</div>
</div>
diff --git a/packages/frontend/src/components/form/Input.tsx b/packages/frontend/src/components/form/Input.tsx
index 200bf54..ab5554c 100644
--- a/packages/frontend/src/components/form/Input.tsx
+++ b/packages/frontend/src/components/form/Input.tsx
@@ -39,7 +39,7 @@ const TextInput = ({ inputType, error, ...rest }: any) => inputType === 'multili
<input {...rest} class={error ? "input is-danger" : "input"} type={inputType} />;
export function Input<T>({ name, readonly, placeholder, tooltip, label, expand, help, children, inputType, inputExtra, side, fromStr = defaultFromString, toStr = defaultToString }: Props<keyof T>): VNode {
- const { error, value, onChange } = useField<T>(name);
+ const { error, value, onChange, required } = useField<T>(name);
return <div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">
@@ -51,7 +51,7 @@ export function Input<T>({ name, readonly, placeholder, tooltip, label, expand,
</div>
<div class="field-body is-flex-grow-3">
<div class="field">
- <p class={expand ? "control is-expanded" : "control"}>
+ <p class={expand ? "control is-expanded has-icons-right" : "control has-icons-right"}>
<TextInput error={error} {...inputExtra}
inputType={inputType}
placeholder={placeholder} readonly={readonly}
@@ -59,6 +59,9 @@ export function Input<T>({ name, readonly, placeholder, tooltip, label, expand,
onChange={(e: h.JSX.TargetedEvent<HTMLInputElement>): void => onChange(fromStr(e.currentTarget.value))} />
{help}
{children}
+ { required && <span class="icon is-danger is-right">
+ <i class="mdi mdi-star"></i>
+ </span> }
</p>
{error && <p class="help is-danger">{error}</p>}
</div>
diff --git a/packages/frontend/src/components/form/InputArray.tsx b/packages/frontend/src/components/form/InputArray.tsx
index cbe5c50..5314eba 100644
--- a/packages/frontend/src/components/form/InputArray.tsx
+++ b/packages/frontend/src/components/form/InputArray.tsx
@@ -20,7 +20,7 @@
*/
import { h, VNode } from "preact";
import { useState } from "preact/hooks";
-import { useTranslator } from "../../i18n";
+import { Translate, useTranslator } from "../../i18n";
import { InputProps, useField } from "./useField";
export interface Props<T> extends InputProps<T> {
@@ -63,24 +63,23 @@ export function InputArray<T>({ name, readonly, placeholder, tooltip, label, hel
placeholder={placeholder} readonly={readonly} disabled={readonly}
name={String(name)} value={currentValue}
onChange={(e): void => setCurrentValue(e.currentTarget.value)} />
- {help}
</p>
<p class="control">
- <button class="button is-info" onClick={(): void => {
+ <button class="button is-info" disabled={!currentValue} onClick={(): void => {
const v = fromStr(currentValue)
if (!isValid(v)) {
setLocalError(i18n`The value ${v} is invalid for a payment url`)
return;
}
setLocalError(null)
-
onChange([v, ...array] as any);
setCurrentValue('');
- }}>add</button>
+ }}><Translate>add</Translate></button>
</p>
</div>
- {error && <p class="help is-danger"> {help} </p>}
- {array.map((v,i) => <div key={i} class="tags has-addons mt-3 mb-0">
+ {help}
+ {error && <p class="help is-danger"> {error} </p>}
+ {array.map((v, i) => <div key={i} class="tags has-addons mt-3 mb-0">
<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(array.filter(f => f !== v) as any);
diff --git a/packages/frontend/src/components/form/InputDate.tsx b/packages/frontend/src/components/form/InputDate.tsx
index 3986433..654d608 100644
--- a/packages/frontend/src/components/form/InputDate.tsx
+++ b/packages/frontend/src/components/form/InputDate.tsx
@@ -78,10 +78,12 @@ export function InputDate<T>({ name, readonly, label, placeholder, help, tooltip
{error && <p class="help is-danger">{error}</p>}
</div>
- { !readonly && <button class="button is-info mr-3" onClick={() => onChange(undefined as any)} ><Translate>clear</Translate></button> }
- {withTimestampSupport &&
+ {!readonly && <span data-tooltip={withTimestampSupport ? i18n`change value to unknown date` : i18n`change value to empty`}>
+ <button class="button is-info mr-3" onClick={() => onChange(undefined as any)} ><Translate>clear</Translate></button>
+ </span>}
+ {withTimestampSupport && <span data-tooltip={i18n`change value to never`}>
<button class="button is-info" onClick={() => onChange({ t_ms: 'never' } as any)}><Translate>never</Translate></button>
- }
+ </span>}
</div>
<DatePicker
opened={opened}
diff --git a/packages/frontend/src/components/form/InputGroup.tsx b/packages/frontend/src/components/form/InputGroup.tsx
index 58e4260..0720cfb 100644
--- a/packages/frontend/src/components/form/InputGroup.tsx
+++ b/packages/frontend/src/components/form/InputGroup.tsx
@@ -25,8 +25,8 @@ import { useGroupField } from "./useGroupField";
export interface Props<T> {
name: T;
children: ComponentChildren;
- label: string;
- tooltip?: string;
+ label: ComponentChildren;
+ tooltip?: ComponentChildren;
alternative?: ComponentChildren;
}
@@ -51,14 +51,10 @@ export function InputGroup<T>({ name, label, children, tooltip, alternative }: P
</button>
</header>
{active ? <div class="card-content">
- <div class="content">
{children}
- </div>
</div> : (
alternative ? <div class="card-content">
- <div class="content">
{alternative}
- </div>
</div> : undefined
)}
</div>;
diff --git a/packages/frontend/src/components/form/InputSearchProduct.tsx b/packages/frontend/src/components/form/InputSearchProduct.tsx
index 87ae722..43f79c9 100644
--- a/packages/frontend/src/components/form/InputSearchProduct.tsx
+++ b/packages/frontend/src/components/form/InputSearchProduct.tsx
@@ -114,18 +114,24 @@ function ProductList({ name, onSelect }: ProductListProps) {
products = <div class="dropdown-content">
{!filtered.length ?
<div class="dropdown-item" >
- <Translate>no results</Translate>
+ <Translate>no products found with that description</Translate>
</div> :
filtered.map(p => (
<div key={p.id} class="dropdown-item" onClick={() => onSelect(p)} style={{ cursor: 'pointer' }}>
- <table>
- <tr>
- <td style={{ width: 32 }}>
- <div class="image" style={{ minWidth: 32 }}><img src={p.image} style={{ width: 32, height: 32 }} /></div>
- </td>
- <td><b>{p.id}</b>: {p.description}</td>
- </tr>
- </table>
+ <article class="media">
+ <div class="media-left">
+ <div class="image" style={{ minWidth: 64 }}><img src={p.image ? p.image : emptyImage} style={{ width: 64, height: 64 }} /></div>
+ </div>
+ <div class="media-content">
+ <div class="content">
+ <p>
+ <strong>{p.id}</strong> <small>{p.price}</small>
+ <br />
+ {p.description}
+ </p>
+ </div>
+ </div>
+ </article>
</div>
))
}
diff --git a/packages/frontend/src/components/form/InputSecured.stories.tsx b/packages/frontend/src/components/form/InputSecured.stories.tsx
index 136d412..7314add 100644
--- a/packages/frontend/src/components/form/InputSecured.stories.tsx
+++ b/packages/frontend/src/components/form/InputSecured.stories.tsx
@@ -35,14 +35,14 @@ export const InitialValueEmpty = (): VNode => {
const [state, setState] = useState<Partial<T>>({ auth_token: '' })
return <FormProvider<T> object={state} errors={{}} valueHandler={setState}>
Initial value: ''
- <InputSecured<T> name="auth_token" label="Token" />
+ <InputSecured<T> name="auth_token" label="Access token" />
</FormProvider>
}
export const InitialValueToken = (): VNode => {
const [state, setState] = useState<Partial<T>>({ auth_token: 'token' })
return <FormProvider<T> object={state} errors={{}} valueHandler={setState}>
- <InputSecured<T> name="auth_token" label="Token" />
+ <InputSecured<T> name="auth_token" label="Access token" />
</FormProvider>
}
@@ -50,6 +50,6 @@ export const InitialValueNull = (): VNode => {
const [state, setState] = useState<Partial<T>>({ auth_token: null })
return <FormProvider<T> object={state} errors={{}} valueHandler={setState}>
Initial value: ''
- <InputSecured<T> name="auth_token" label="Token" />
+ <InputSecured<T> name="auth_token" label="Access token" />
</FormProvider>
}
diff --git a/packages/frontend/src/components/form/InputSecured.tsx b/packages/frontend/src/components/form/InputSecured.tsx
index 64737e3..7d8d655 100644
--- a/packages/frontend/src/components/form/InputSecured.tsx
+++ b/packages/frontend/src/components/form/InputSecured.tsx
@@ -57,7 +57,7 @@ export function InputSecured<T>({ name, readonly, placeholder, tooltip, label, h
<div class="field has-addons">
<button class="button" onClick={(): void => { setActive(!active); }} >
<div class="icon is-left"><i class="mdi mdi-lock-reset" /></div>
- <span><Translate>Manage token</Translate></span>
+ <span><Translate>Manage access token</Translate></span>
</button>
<TokenStatus prev={initial} post={value} />
</div>
diff --git a/packages/frontend/src/components/form/InputStock.tsx b/packages/frontend/src/components/form/InputStock.tsx
index ce3add8..b323012 100644
--- a/packages/frontend/src/components/form/InputStock.tsx
+++ b/packages/frontend/src/components/form/InputStock.tsx
@@ -103,23 +103,25 @@ export function InputStock<T>({ name, readonly, placeholder, tooltip, label, hel
const stockAddedErrors: FormErrors<typeof addedStock> = {
lost: currentStock + addedStock.incoming < addedStock.lost ?
- i18n`lost cannot be greater that current + incoming (max ${currentStock + addedStock.incoming})`
+ i18n`lost cannot be greater than current and incoming (max ${currentStock + addedStock.incoming})`
: undefined
}
- const stockUpdateDescription = stockAddedErrors.lost ? '' : (
- !!addedStock.incoming || !!addedStock.lost ?
- i18n`current stock will change from ${currentStock} to ${currentStock + addedStock.incoming - addedStock.lost}` :
- i18n`current stock will stay at ${currentStock}`
- )
+ // const stockUpdateDescription = stockAddedErrors.lost ? '' : (
+ // !!addedStock.incoming || !!addedStock.lost ?
+ // i18n`current stock will change from ${currentStock} to ${currentStock + addedStock.incoming - addedStock.lost}` :
+ // i18n`current stock will stay at ${currentStock}`
+ // )
return <Fragment>
<div class="card">
<header class="card-header">
- {label}
- {tooltip && <span class="icon" data-tooltip={tooltip}>
- <i class="mdi mdi-information" />
- </span>}
+ <p class="card-header-title">
+ {label}
+ {tooltip && <span class="icon" data-tooltip={tooltip}>
+ <i class="mdi mdi-information" />
+ </span>}
+ </p>
</header>
<div class="card-content">
<FormProvider<Entity> name="stock" errors={errors} object={formValue} valueHandler={valueHandler}>
@@ -130,14 +132,15 @@ export function InputStock<T>({ name, readonly, placeholder, tooltip, label, hel
<InputNumber name="lost" label={i18n`Lost`} />
</FormProvider>
- <div class="field is-horizontal">
+ {/* <div class="field is-horizontal">
<div class="field-label is-normal" />
<div class="field-body is-flex-grow-3">
<div class="field">
{stockUpdateDescription}
</div>
</div>
- </div>
+ </div> */}
+
</Fragment> : <InputNumber<Entity> name="current"
label={i18n`Current`}
side={
diff --git a/packages/frontend/src/components/form/InputWithAddon.tsx b/packages/frontend/src/components/form/InputWithAddon.tsx
index 83b6c2e..22a4960 100644
--- a/packages/frontend/src/components/form/InputWithAddon.tsx
+++ b/packages/frontend/src/components/form/InputWithAddon.tsx
@@ -38,7 +38,7 @@ const defaultToString = (f?: any): string => f || ''
const defaultFromString = (v: string): any => v as any
export function InputWithAddon<T>({ name, readonly, addonBefore, children, expand, label, placeholder, help, tooltip, inputType, inputExtra, side, addonAfter, toStr = defaultToString, fromStr = defaultFromString }: Props<keyof T>): VNode {
- const { error, value, onChange } = useField<T>(name);
+ const { error, value, onChange, required } = useField<T>(name);
return <div class="field is-horizontal">
<div class="field-label is-normal">
@@ -55,11 +55,14 @@ export function InputWithAddon<T>({ name, readonly, addonBefore, children, expan
{addonBefore && <div class="control">
<a class="button is-static">{addonBefore}</a>
</div>}
- <p class={expand ? "control is-expanded" : "control"}>
+ <p class={`control${expand ? " is-expanded" :""}${required ? " has-icons-right" : ''}`}>
<input {...(inputExtra || {})} class={error ? "input is-danger" : "input"} type={inputType}
placeholder={placeholder} readonly={readonly}
name={String(name)} value={toStr(value)}
onChange={(e): void => onChange(fromStr(e.currentTarget.value))} />
+ {required && <span class="icon is-danger is-right">
+ <i class="mdi mdi-star"></i>
+ </span>}
{help}
{children}
</p>
diff --git a/packages/frontend/src/components/form/TextField.tsx b/packages/frontend/src/components/form/TextField.tsx
new file mode 100644
index 0000000..50ea26a
--- /dev/null
+++ b/packages/frontend/src/components/form/TextField.tsx
@@ -0,0 +1,53 @@
+/*
+ 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 { ComponentChildren, h, VNode } from "preact";
+import { useField, InputProps } from "./useField";
+
+interface Props<T> extends InputProps<T> {
+ inputType?: 'text' | 'number' | 'multiline' | 'password';
+ expand?: boolean;
+ side?: ComponentChildren;
+ children: ComponentChildren;
+}
+
+export function TextField<T>({ name, tooltip, label, expand, help, children, side}: Props<keyof T>): VNode {
+ const { error } = useField<T>(name);
+ return <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label">
+ {label}
+ {tooltip && <span class="icon" data-tooltip={tooltip}>
+ <i class="mdi mdi-information" />
+ </span>}
+ </label>
+ </div>
+ <div class="field-body is-flex-grow-3">
+ <div class="field">
+ <p class={expand ? "control is-expanded has-icons-right" : "control has-icons-right"}>
+ {children}
+ {help}
+ </p>
+ {error && <p class="help is-danger">{error}</p>}
+ </div>
+ {side}
+ </div>
+ </div>;
+}
diff --git a/packages/frontend/src/components/form/useField.tsx b/packages/frontend/src/components/form/useField.tsx
index 969291c..c552711 100644
--- a/packages/frontend/src/components/form/useField.tsx
+++ b/packages/frontend/src/components/form/useField.tsx
@@ -24,6 +24,7 @@ import { useFormContext } from "./FormProvider";
interface Use<V> {
error?: string;
+ required: boolean;
value: any;
initial: any;
onChange: (v: V) => void;
@@ -47,9 +48,10 @@ export function useField<T>(name: keyof T): Use<T[typeof name]> {
const value = readField(object, String(name))
const initial = readField(initialObject, String(name))
const isDirty = value !== initial
-
+ const hasError = readField(errors, String(name))
return {
- error: isDirty ? readField(errors, String(name)) : undefined,
+ error: isDirty ? hasError : undefined,
+ required: !isDirty && hasError,
value,
initial,
onChange: updateField(name) as any,
@@ -76,9 +78,9 @@ const setValueDeeper = (object: any, names: string[], value: any): any => {
export interface InputProps<T> {
name: T;
- label: string;
+ label: ComponentChildren;
placeholder?: string;
- tooltip?: string;
+ tooltip?: ComponentChildren;
readonly?: boolean;
help?: ComponentChildren;
} \ No newline at end of file
diff --git a/packages/frontend/src/components/instance/DefaultInstanceFormFields.tsx b/packages/frontend/src/components/instance/DefaultInstanceFormFields.tsx
new file mode 100644
index 0000000..2b98383
--- /dev/null
+++ b/packages/frontend/src/components/instance/DefaultInstanceFormFields.tsx
@@ -0,0 +1,88 @@
+/*
+ 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 { 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 { 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();
+ const backend = useBackendContext();
+ return <Fragment>
+ { showId && <InputWithAddon<Entity> name="id" label={i18n`ID`} addonBefore={`${backend.url}/private/instances/`} readonly={readonlyId} tooltip={i18n`display name identification`} /> }
+
+ <Input<Entity> name="name" label={i18n`Name`} tooltip={i18n`descriptive name`} />
+
+ <InputPayto<Entity> name="payto_uris" label={i18n`Account address`} help="x-taler-bank/bank.taler:5882/blogger" tooltip={i18n`where the money will be sent`} />
+
+ <InputCurrency<Entity> name="default_max_deposit_fee" label={i18n`Default max deposit fee`} tooltip={i18n`max deposit fee when an order has not overridden it`} />
+
+ <InputCurrency<Entity> name="default_max_wire_fee" label={i18n`Default max wire fee`} tooltip={i18n`max wire fee when the order has not overridden it`} />
+
+ <Input<Entity> name="default_wire_fee_amortization" label={i18n`Default wire fee amortization`} tooltip={i18n`max wire fee amortization when the order has not overridden it`} />
+
+ <InputGroup name="address" label={i18n`Address`} tooltip={i18n`physical location of merchant`}>
+ <InputLocation name="address" />
+ </InputGroup>
+
+ <InputGroup name="jurisdiction" label={i18n`Jurisdiction`} tooltip={i18n`legal location of merchant`}>
+ <InputLocation name="jurisdiction" />
+ </InputGroup>
+
+ <InputDuration<Entity> name="default_pay_delay" label={i18n`Default payment delay`} tooltip={i18n`max time to pay if the order does not override it`} />
+
+ <InputDuration<Entity> name="default_wire_transfer_delay" label={i18n`Default wire transfer delay`} tooltip={i18n`min time to wait the transfer if the merchant does not override it`} />
+
+ </Fragment>;
+}
+/*
+ <InputWithAddon<Entity> name="id" label={i18n`Identifier`} addonBefore={`${backend.url}/private/instances/`} readonly={!!forceId} tooltip={i18n`Name of the instance in URLs. The 'default' instance is special in that it is used to administer other instances.`} />
+
+ <Input<Entity> name="name" label={i18n`Business name`} tooltip={i18n`Legal name of the business represented by this instance.`} />
+
+ <InputPayto<Entity> name="payto_uris" label={i18n`Bank account URI`} help="x-taler-bank/bank.taler:5882/blogger" tooltip={i18n`URI specifying bank account for crediting revenue.`} />
+
+ <InputCurrency<Entity> name="default_max_deposit_fee" label={i18n`Default max deposit fee`} tooltip={i18n`Maximum deposit fees this merchant is willing to pay per order by default.`} />
+
+ <InputCurrency<Entity> name="default_max_wire_fee" label={i18n`Default max wire fee`} tooltip={i18n`Maximum wire fees this merchant is willing to pay per wire transfer by default.`} />
+
+ <Input<Entity> name="default_wire_fee_amortization" label={i18n`Default wire fee amortization`} tooltip={i18n`Number of orders excess wire transfer fees will be divided by to compute per order surcharge.`} />
+
+ <InputGroup name="address" label={i18n`Address`} tooltip={i18n`Physical location of the merchant.`}>
+ <InputLocation name="address" />
+ </InputGroup>
+
+ <InputGroup name="jurisdiction" label={i18n`Jurisdiction`} tooltip={i18n`Jurisdiction for legal disputes with the merchant.`}>
+ <InputLocation name="jurisdiction" />
+ </InputGroup>
+
+ <InputDuration<Entity> name="default_pay_delay" label={i18n`Default payment delay`} tooltip={i18n`Time customers have to pay an order before the offer expires by default.`} />
+
+ <InputDuration<Entity> name="default_wire_transfer_delay" label={i18n`Default wire transfer delay`} tooltip={i18n`Maximum time an exchange is allowed to delay wiring funds to the merchant, enabling it to aggregate smaller payments into larger wire transfers and reducing wire fees.`} />
+*/ \ No newline at end of file
diff --git a/packages/frontend/src/components/menu/index.tsx b/packages/frontend/src/components/menu/index.tsx
index 31826ed..3a7ccab 100644
--- a/packages/frontend/src/components/menu/index.tsx
+++ b/packages/frontend/src/components/menu/index.tsx
@@ -27,15 +27,12 @@ import { Sidebar } from "./SideBar";
function getInstanceTitle(path: string, id: string): string {
switch (path) {
- // case InstancePaths.details: return `${id}`
case InstancePaths.update: return `${id}: Settings`
case InstancePaths.order_list: return `${id}: Orders`
case InstancePaths.order_new: return `${id}: New order`
- case InstancePaths.order_details: return `${id}: Detail of the order`
case InstancePaths.product_list: return `${id}: Products`
case InstancePaths.product_new: return `${id}: New product`
case InstancePaths.product_update: return `${id}: Update product`
- case InstancePaths.reserves_details: return `${id}: Detail of a reserve`
case InstancePaths.reserves_new: return `${id}: New reserve`
case InstancePaths.reserves_list: return `${id}: Reserves`
case InstancePaths.transfers_list: return `${id}: Transfers`
diff --git a/packages/frontend/src/components/modal/index.tsx b/packages/frontend/src/components/modal/index.tsx
index 8f6b722..9874a19 100644
--- a/packages/frontend/src/components/modal/index.tsx
+++ b/packages/frontend/src/components/modal/index.tsx
@@ -44,7 +44,7 @@ export function ConfirmModal({ active, description, onCancel, onConfirm, childre
<div class="modal-background " onClick={onCancel} />
<div class="modal-card">
<header class="modal-card-head">
- {!description ? null : <p class="modal-card-title has-text-white-ter">{description}</p>}
+ {!description ? null : <p class="modal-card-title">{description}</p>}
<button class="delete " aria-label="close" onClick={onCancel} />
</header>
<section class="modal-card-body">
@@ -125,6 +125,7 @@ interface UpdateTokenModalProps {
onClear: () => void;
}
+//FIXME: merge UpdateTokenModal with SetTokenNewInstanceModal
export function UpdateTokenModal({ onCancel, onClear, onConfirm, oldToken }: UpdateTokenModalProps): VNode {
type State = { old_token: string, new_token: string, repeat_token: string }
const [form, setValue] = useState<Partial<State>>({
@@ -134,7 +135,7 @@ export function UpdateTokenModal({ onCancel, onClear, onConfirm, oldToken }: Upd
const hasInputTheCorrectOldToken = oldToken && oldToken !== form.old_token
const errors = {
- old_token: hasInputTheCorrectOldToken ? i18n`is not the same as the current token` : undefined,
+ old_token: hasInputTheCorrectOldToken ? i18n`is not the same as the current access token` : undefined,
new_token: !form.new_token ? i18n`cannot be empty` : (form.new_token === form.old_token ? i18n`cannot be the same as the old token` : undefined),
repeat_token: form.new_token !== form.repeat_token ? i18n`is not the same` : undefined
}
@@ -143,7 +144,7 @@ export function UpdateTokenModal({ onCancel, onClear, onConfirm, oldToken }: Upd
const instance = useInstanceContext()
- const text = i18n`You are updating the authorization token from instance with id ${instance.id}`
+ const text = i18n`You are updating the access token from instance with id ${instance.id}`
return <ClearConfirmModal description={text}
onCancel={onCancel}
@@ -154,11 +155,11 @@ export function UpdateTokenModal({ onCancel, onClear, onConfirm, oldToken }: Upd
<div class="column" />
<div class="column is-four-fifths" >
<FormProvider errors={errors} object={form} valueHandler={setValue}>
- {oldToken && <Input<State> name="old_token" label={i18n`Old token`} tooltip={i18n`token currently in use`} inputType="password" />}
- <Input<State> name="new_token" label={i18n`New token`} tooltip={i18n`next token to be used`} inputType="password" />
- <Input<State> name="repeat_token" label={i18n`Repeat token`} tooltip={i18n`confirm the same token`} inputType="password" />
+ {oldToken && <Input<State> name="old_token" label={i18n`Old access token`} tooltip={i18n`access token currently in use`} inputType="password" />}
+ <Input<State> name="new_token" label={i18n`New access token`} tooltip={i18n`next access token to be used`} inputType="password" />
+ <Input<State> name="repeat_token" label={i18n`Repeat access token`} tooltip={i18n`confirm the same access token`} inputType="password" />
</FormProvider>
- <p><Translate>Clearing the auth token will mean public access to the instance</Translate></p>
+ <p><Translate>Clearing the access token will mean public access to the instance</Translate></p>
</div>
<div class="column" />
</div>
@@ -173,13 +174,13 @@ export function SetTokenNewInstanceModal({ onCancel, onClear, onConfirm }: Updat
const i18n = useTranslator()
const errors = {
- new_token: !form.new_token ? i18n`cannot be empty` : (form.new_token === form.old_token ? i18n`cannot be the same as the old token` : undefined),
+ new_token: !form.new_token ? i18n`cannot be empty` : (form.new_token === form.old_token ? i18n`cannot be the same as the old access token` : undefined),
repeat_token: form.new_token !== form.repeat_token ? i18n`is not the same` : undefined
}
const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== undefined)
- const text = i18n`You are setting the authorization token for the new instance`
+ const text = i18n`You are setting the access token for the new instance`
return <ClearConfirmModal description={text}
onCancel={onCancel}
@@ -190,10 +191,10 @@ export function SetTokenNewInstanceModal({ onCancel, onClear, onConfirm }: Updat
<div class="column" />
<div class="column is-four-fifths" >
<FormProvider errors={errors} object={form} valueHandler={setValue}>
- <Input<State> name="new_token" label={i18n`New token`} tooltip={i18n`next token to be used`} inputType="password" />
- <Input<State> name="repeat_token" label={i18n`Repeat token`} tooltip={i18n`confirm the same token`} inputType="password" />
+ <Input<State> name="new_token" label={i18n`New access token`} tooltip={i18n`next access token to be used`} inputType="password" />
+ <Input<State> name="repeat_token" label={i18n`Repeat access token`} tooltip={i18n`confirm the same access token`} inputType="password" />
</FormProvider>
- <p><Translate>Clearing the auth token will mean public access to the instance</Translate></p>
+ <p><Translate>Clearing the access token will mean public access to the instance</Translate></p>
</div>
<div class="column" />
</div>
diff --git a/packages/frontend/src/context/backend.ts b/packages/frontend/src/context/backend.ts
index c1b2c14..9355859 100644
--- a/packages/frontend/src/context/backend.ts
+++ b/packages/frontend/src/context/backend.ts
@@ -49,7 +49,6 @@ export function useBackendContextState(): BackendContextType {
const [url, triedToLog, changeBackend, resetBackend] = useBackendURL();
const [token, _updateToken] = useBackendDefaultToken();
const updateToken = (t?:string) => {
- // console.log("update token", t)
_updateToken(t)
}
diff --git a/packages/frontend/src/declaration.d.ts b/packages/frontend/src/declaration.d.ts
index 21199f4..82bc694 100644
--- a/packages/frontend/src/declaration.d.ts
+++ b/packages/frontend/src/declaration.d.ts
@@ -685,6 +685,15 @@ export namespace MerchantBackend {
// The order was neither claimed nor paid.
order_status: "unpaid";
+ // when was the order created
+ creation_time: Timestamp;
+
+ // Order summary text.
+ summary: string;
+
+ // Total amount of the order (to be paid by the customer).
+ total_amount: Amount;
+
// URI that the wallet must process to complete the payment.
taler_pay_uri: string;
diff --git a/packages/frontend/src/hooks/product.ts b/packages/frontend/src/hooks/product.ts
index 6dd2d13..cdb1c4e 100644
--- a/packages/frontend/src/hooks/product.ts
+++ b/packages/frontend/src/hooks/product.ts
@@ -138,7 +138,7 @@ export function useInstanceProducts(): HttpResponse<(MerchantBackend.Products.Pr
if (!list?.data || !list.data.products.length || listError || listLoading) return null
return [`/private/products/${list.data.products[pageIndex].product_id}`, token, url]
}, fetcher, {
-
+ revalidateAll: true,
})
useEffect(() => {
diff --git a/packages/frontend/src/i18n/index.tsx b/packages/frontend/src/i18n/index.tsx
index 5be1a3b..63c8e19 100644
--- a/packages/frontend/src/i18n/index.tsx
+++ b/packages/frontend/src/i18n/index.tsx
@@ -21,7 +21,7 @@
/**
* Imports
*/
-import { Component, ComponentChild, ComponentChildren, h, Fragment, VNode } from "preact";
+import { ComponentChild, ComponentChildren, h, Fragment, VNode } from "preact";
import { useTranslationContext } from "../context/translation";
@@ -40,11 +40,10 @@ export function useTranslator() {
}
-
/**
* Convert template strings to a msgid
*/
-function toI18nString(stringSeq: ReadonlyArray<string>): string {
+ function toI18nString(stringSeq: ReadonlyArray<string>): string {
let s = "";
for (let i = 0; i < stringSeq.length; i++) {
s += stringSeq[i];
diff --git a/packages/frontend/src/paths/admin/create/CreatePage.tsx b/packages/frontend/src/paths/admin/create/CreatePage.tsx
index 488d137..82286de 100644
--- a/packages/frontend/src/paths/admin/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/admin/create/CreatePage.tsx
@@ -24,22 +24,13 @@ import { useState } from "preact/hooks";
import * as yup from 'yup';
import { AsyncButton } from "../../../components/exception/AsyncButton";
import { FormErrors, FormProvider } from "../../../components/form/FormProvider";
-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";
-import { SetTokenNewInstanceModal, UpdateTokenModal } from "../../../components/modal";
-import { useBackendContext } from "../../../context/backend";
-import { useInstanceContext } from "../../../context/instance";
+import { SetTokenNewInstanceModal } from "../../../components/modal";
import { MerchantBackend } from "../../../declaration";
import { Translate, useTranslator } from "../../../i18n";
import { InstanceCreateSchema as schema } from '../../../schemas';
+import { DefaultInstanceFormFields } from "../../../components/instance/DefaultInstanceFormFields";
-type Entity = MerchantBackend.Instances.InstanceConfigurationMessage & { auth_token?: string }
+export type Entity = MerchantBackend.Instances.InstanceConfigurationMessage & { auth_token?: string }
interface Props {
onCreate: (d: Entity) => Promise<void>;
@@ -50,9 +41,9 @@ interface Props {
function with_defaults(id?: string): Partial<Entity> {
return {
id,
- default_pay_delay: { d_ms: 1000 * 60 * 5 },
+ default_pay_delay: { d_ms: 1000 * 60 * 60 }, // one hour
default_wire_fee_amortization: 1,
- default_wire_transfer_delay: { d_ms: 2000 * 60 * 5 },
+ default_wire_transfer_delay: { d_ms: 1000 * 2 * 60 * 60 * 24 }, // one day
};
}
export function CreatePage({ onCreate, onBack, forceId }: Props): VNode {
@@ -78,13 +69,13 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode {
// schema.validateSync(value, { abortEarly: false })
return onCreate(schema.cast(value) as Entity);
}
- const backend = useBackendContext()
function updateToken(token: string | null) {
valueHandler(old => ({ ...old, auth_token: token === null ? undefined : token }))
}
const i18n = useTranslator()
+
return <div>
<div class="columns">
<div class="column" />
@@ -108,44 +99,37 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode {
<div class="column" />
</div>
+ <section class="hero is-hero-bar">
+ <div class="hero-body">
+ <div class="level">
+ <div class="level-item has-text-centered">
+ <h1 class="title">
+ <button class="button is-danger" onClick={() => updateIsTokenDialogActive(true)} >
+ <div class="icon is-centered"><i class="mdi mdi-lock-reset" /></div>
+ <span><Translate>Set access token</Translate></span>
+ </button>
+ </h1>
+ </div>
+ </div>
+ </div></section>
+
+
<section class="section is-main-section">
<div class="columns">
<div class="column" />
- <div class="column is-two-thirds">
- <FormProvider<Entity> errors={errors} object={value} valueHandler={valueHandler} >
-
- <InputWithAddon<Entity> name="id" label={i18n`Identifier`} addonBefore={`${backend.url}/private/instances/`} readonly={!!forceId} tooltip={i18n`Name of the instance in URLs. The 'default' instance is special in that it is used to administer other instances.`} />
-
- <Input<Entity> name="name" label={i18n`Business name`} tooltip={i18n`Legal name of the business represented by this instance.`} />
+ <div class="column is-four-fifths">
- <InputPayto<Entity> name="payto_uris" label={i18n`Bank account URI`} help="x-taler-bank/bank.taler:5882/blogger" tooltip={i18n`URI specifying bank account for crediting revenue.`} />
-
- <InputCurrency<Entity> name="default_max_deposit_fee" label={i18n`Default max deposit fee`} tooltip={i18n`Maximum deposit fees this merchant is willing to pay per order by default.`} />
-
- <InputCurrency<Entity> name="default_max_wire_fee" label={i18n`Default max wire fee`} tooltip={i18n`Maximum wire fees this merchant is willing to pay per wire transfer by default.`} />
-
- <Input<Entity> name="default_wire_fee_amortization" label={i18n`Default wire fee amortization`} tooltip={i18n`Number of orders excess wire transfer fees will be divided by to compute per order surcharge.`} />
-
- <InputGroup name="address" label={i18n`Address`} tooltip={i18n`Physical location of the merchant.`}>
- <InputLocation name="address" />
- </InputGroup>
-
- <InputGroup name="jurisdiction" label={i18n`Jurisdiction`} tooltip={i18n`Jurisdiction for legal disputes with the merchant.`}>
- <InputLocation name="jurisdiction" />
- </InputGroup>
-
- <InputDuration<Entity> name="default_pay_delay" label={i18n`Default payment delay`} tooltip={i18n`Time customers have to pay an order before the offer expires by default.`} />
+ <FormProvider<Entity> errors={errors} object={value} valueHandler={valueHandler} >
- <InputDuration<Entity> name="default_wire_transfer_delay" label={i18n`Default wire transfer delay`} tooltip={i18n`Maximum time an exchange is allowed to delay wiring funds to the merchant, enabling it to aggregate smaller payments into larger wire transfers and reducing wire fees.`} />
+ <DefaultInstanceFormFields readonlyId={!!forceId} showId={true} />
</FormProvider>
<div class="buttons is-right mt-5">
- {onBack && <button class="button" onClick={onBack} ><Translate>Cancel</Translate></button>}
- {isTokenSet ?
- <AsyncButton onClick={submit} disabled={hasErrors} ><Translate>Confirm</Translate></AsyncButton> :
- <button class="button" onClick={() => updateIsTokenDialogActive(true)} ><Translate>Set token</Translate></button>
- }
+ {onBack && <button class="button" onClick={onBack}><Translate>Cancel</Translate></button>}
+ <AsyncButton onClick={submit} disabled={!isTokenSet && hasErrors} data-tooltip={
+ hasErrors ? i18n`Need to complete fields first, check fields marked with a star` : 'confirm operation'
+ }><Translate>Confirm</Translate></AsyncButton>
</div>
</div>
diff --git a/packages/frontend/src/paths/admin/create/InstanceCreatedSuccessfully.tsx b/packages/frontend/src/paths/admin/create/InstanceCreatedSuccessfully.tsx
index 45aec1c..00b3f20 100644
--- a/packages/frontend/src/paths/admin/create/InstanceCreatedSuccessfully.tsx
+++ b/packages/frontend/src/paths/admin/create/InstanceCreatedSuccessfully.tsx
@@ -49,7 +49,7 @@ export function InstanceCreatedSuccessfully({ entity, onConfirm }: { entity: Ent
</div>
<div class="field is-horizontal">
<div class="field-label is-normal">
- <label class="label">Token</label>
+ <label class="label">Access token</label>
</div>
<div class="field-body is-flex-grow-3">
<div class="field">
diff --git a/packages/frontend/src/paths/admin/list/Table.tsx b/packages/frontend/src/paths/admin/list/Table.tsx
index 1f512c0..4210a92 100644
--- a/packages/frontend/src/paths/admin/list/Table.tsx
+++ b/packages/frontend/src/paths/admin/list/Table.tsx
@@ -22,7 +22,7 @@
import { h, VNode } from "preact";
import { StateUpdater, useEffect, useState } from "preact/hooks";
import { MerchantBackend } from "../../../declaration";
-import { Translate } from "../../../i18n";
+import { Translate, useTranslator } from "../../../i18n";
interface Props {
instances: MerchantBackend.Instances.Instance[];
@@ -50,6 +50,7 @@ export function CardTable({ instances, onCreate, onUpdate, onDelete, selected }:
}
}, [actionQueue, selected, onUpdate])
+ const i18n = useTranslator()
return <div class="card has-table">
<header class="card-header">
@@ -63,9 +64,11 @@ export function CardTable({ instances, onCreate, onUpdate, onDelete, selected }:
</button>
</div>
<div class="card-header-icon" aria-label="more options">
- <button class="button is-info" type="button" onClick={onCreate}>
- <span class="icon is-small" ><i class="mdi mdi-plus mdi-36px" /></span>
- </button>
+ <span class="has-tooltip-left" data-tooltip={i18n`add new instance`}>
+ <button class="button is-info" type="button" onClick={onCreate}>
+ <span class="icon is-small" ><i class="mdi mdi-plus mdi-36px" /></span>
+ </button>
+ </span>
</div>
</header>
@@ -96,48 +99,48 @@ function toggleSelected<T>(id: T): (prev: T[]) => T[] {
function Table({ rowSelection, rowSelectionHandler, instances, onUpdate, onDelete }: TableProps): VNode {
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">
+ <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.indexOf(i.id) != -1} onClick={(): void => rowSelectionHandler(toggleSelected(i.id))} />
+ <input type="checkbox" checked={rowSelection.length === instances.length} onClick={(): void => rowSelectionHandler(rowSelection.length === instances.length ? [] : instances.map(i => i.id))} />
<span class="check" />
</label>
- </td>
- <td><a href={`/orders?instance=${i.id}`} >{i.id}</a></td>
- <td >{i.name}</td>
- <td class="is-actions-cell right-sticky">
- <div class="buttons is-right">
- <button class="button is-small is-success jb-modal" type="button" onClick={(): void => onUpdate(i.id)}>
- <Translate>Edit</Translate>
- </button>
- <button class="button is-small is-danger jb-modal" type="button" onClick={(): void => onDelete(i)}>
- <Translate>Delete</Translate>
- </button>
- </div>
- </td>
+ </th>
+ <th><Translate>ID</Translate></th>
+ <th><Translate>Name</Translate></th>
+ <th />
</tr>
- })}
-
- </tbody>
- </table>
+ </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><a href={`/orders?instance=${i.id}`} >{i.id}</a></td>
+ <td >{i.name}</td>
+ <td class="is-actions-cell right-sticky">
+ <div class="buttons is-right">
+ <button class="button is-small is-success jb-modal" type="button" onClick={(): void => onUpdate(i.id)}>
+ <Translate>Edit</Translate>
+ </button>
+ <button class="button is-small is-danger jb-modal" type="button" onClick={(): void => onDelete(i)}>
+ <Translate>Delete</Translate>
+ </button>
+ </div>
+ </td>
+ </tr>
+ })}
+
+ </tbody>
+ </table>
</div>
- )
+ )
}
function EmptyTable(): VNode {
diff --git a/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx b/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
index 1b47564..ae32dac 100644
--- a/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
@@ -257,7 +257,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
with a total price of {totalPriceProducts}
</p>
} tooltip={i18n`Add products without inventory management to the order.`}>
- <NonInventoryProductFrom value={editingProduct} onAddProduct={(p) => {
+ <NonInventoryProductFrom productToEdit={editingProduct} onAddProduct={(p) => {
setEditingProduct(undefined)
return addNewProduct(p)
}} />
diff --git a/packages/frontend/src/paths/instance/orders/create/InventoryProductForm.tsx b/packages/frontend/src/paths/instance/orders/create/InventoryProductForm.tsx
index 6cf48f8..b97b39a 100644
--- a/packages/frontend/src/paths/instance/orders/create/InventoryProductForm.tsx
+++ b/packages/frontend/src/paths/instance/orders/create/InventoryProductForm.tsx
@@ -39,12 +39,14 @@ export function InventoryProductForm({ currentProducts, onAddProduct }: Props):
const i18n = useTranslator()
+ const productWithInfiniteStock = state.product && state.product.total_stock === -1
+
const submit = (): void => {
if (!state.product) {
setErrors({ product: i18n`You must enter a valid product identifier.` });
return;
}
- if (state.product.total_stock === -1) {
+ if (productWithInfiniteStock) {
onAddProduct(state.product, 1)
} else {
if (!state.quantity || state.quantity <= 0) {
@@ -73,9 +75,9 @@ export function InventoryProductForm({ currentProducts, onAddProduct }: Props):
return <FormProvider<Form> errors={errors} object={state} valueHandler={setState}>
<InputSearchProduct selected={state.product} onChange={(p) => setState(v => ({ ...v, product: p }))} />
- <InputNumber<Form> name="quantity" label={i18n`Quantity`} tooltip={i18n`how many products will be added`} />
+ { state.product && !productWithInfiniteStock && <InputNumber<Form> name="quantity" label={i18n`Quantity`} tooltip={i18n`how many products will be added`} /> }
<div class="buttons is-right mt-5">
- <button class="button is-success" onClick={submit}><Translate>Add</Translate></button>
+ <button class="button is-success" disabled={!state.product} onClick={submit}><Translate>Add</Translate></button>
</div>
</FormProvider>
}
diff --git a/packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx b/packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx
index 967f1cb..756ec23 100644
--- a/packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx
+++ b/packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx
@@ -28,22 +28,22 @@ import {
NonInventoryProductSchema as schema
} from '../../../../schemas';
import * as yup from 'yup';
-import { useTranslator } from "../../../../i18n";
+import { Translate, useTranslator } from "../../../../i18n";
type Entity = MerchantBackend.Product
interface Props {
onAddProduct: (p: Entity) => Promise<void>;
- value?: Entity;
+ productToEdit?: Entity;
}
-export function NonInventoryProductFrom({ value, onAddProduct }: Props): VNode {
+export function NonInventoryProductFrom({ productToEdit, onAddProduct }: Props): VNode {
const [showCreateProduct, setShowCreateProduct] = useState(false)
- const editing = !!value
+ const isEditing = !!productToEdit
useEffect(() => {
- setShowCreateProduct(editing)
- }, [editing])
+ setShowCreateProduct(isEditing)
+ }, [isEditing])
const [submitForm, addFormSubmitter] = useListener<Partial<MerchantBackend.Product> | undefined>((result) => {
if (result) {
@@ -62,10 +62,12 @@ export function NonInventoryProductFrom({ value, onAddProduct }: Props): VNode {
return <Fragment>
<div class="buttons">
- <button class="button is-success" onClick={() => setShowCreateProduct(true)} >add new product</button>
+ <button class="button is-success" onClick={() => setShowCreateProduct(true)} ><Translate>add new product</Translate></button>
</div>
- {showCreateProduct && <ConfirmModal active onCancel={() => setShowCreateProduct(false)} onConfirm={submitForm}>
- <ProductForm initial={value} onSubscribe={addFormSubmitter} />
+ {showCreateProduct && <ConfirmModal active
+ description="alskdj alsk jdalksjd laksjd lkasjd"
+ onCancel={() => setShowCreateProduct(false)} onConfirm={submitForm}>
+ <ProductForm initial={productToEdit} onSubscribe={addFormSubmitter} />
</ConfirmModal>}
</Fragment>
}
diff --git a/packages/frontend/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx b/packages/frontend/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx
index db0be90..14c5d68 100644
--- a/packages/frontend/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx
+++ b/packages/frontend/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx
@@ -17,6 +17,7 @@ import { h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";
import { CreatedSuccessfully } from "../../../../components/notifications/CreatedSuccessfully";
import { useOrderAPI } from "../../../../hooks/order";
+import { Translate } from "../../../../i18n";
import { Entity } from "./index";
interface Props {
@@ -38,7 +39,7 @@ export function OrderCreatedSuccessfully({ entity, onConfirm, onCreateAnother }:
return <CreatedSuccessfully onConfirm={onConfirm} onCreateAnother={onCreateAnother}>
<div class="field is-horizontal">
<div class="field-label is-normal">
- <label class="label">Amount</label>
+ <label class="label"><Translate>Amount</Translate></label>
</div>
<div class="field-body is-flex-grow-3">
<div class="field">
@@ -50,7 +51,7 @@ export function OrderCreatedSuccessfully({ entity, onConfirm, onCreateAnother }:
</div>
<div class="field is-horizontal">
<div class="field-label is-normal">
- <label class="label">Summary</label>
+ <label class="label"><Translate>Summary</Translate></label>
</div>
<div class="field-body is-flex-grow-3">
<div class="field">
@@ -62,7 +63,7 @@ export function OrderCreatedSuccessfully({ entity, onConfirm, onCreateAnother }:
</div>
<div class="field is-horizontal">
<div class="field-label is-normal">
- <label class="label">Order ID</label>
+ <label class="label"><Translate>Order ID</Translate></label>
</div>
<div class="field-body is-flex-grow-3">
<div class="field">
@@ -74,7 +75,7 @@ export function OrderCreatedSuccessfully({ entity, onConfirm, onCreateAnother }:
</div>
<div class="field is-horizontal">
<div class="field-label is-normal">
- <label class="label">Payment URL</label>
+ <label class="label"><Translate>Payment URL</Translate></label>
</div>
<div class="field-body is-flex-grow-3">
<div class="field">
diff --git a/packages/frontend/src/paths/instance/orders/details/DetailPage.tsx b/packages/frontend/src/paths/instance/orders/details/DetailPage.tsx
index be05b43..9f148b6 100644
--- a/packages/frontend/src/paths/instance/orders/details/DetailPage.tsx
+++ b/packages/frontend/src/paths/instance/orders/details/DetailPage.tsx
@@ -19,6 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
+import { AmountJson, Amounts } from "@gnu-taler/taler-util";
import { format } from "date-fns";
import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
@@ -29,7 +30,7 @@ import { InputDate } from "../../../../components/form/InputDate";
import { InputDuration } from "../../../../components/form/InputDuration";
import { InputGroup } from "../../../../components/form/InputGroup";
import { InputLocation } from "../../../../components/form/InputLocation";
-import { NotificationCard } from "../../../../components/menu";
+import { TextField } from "../../../../components/form/TextField";
import { ProductList } from "../../../../components/product/ProductList";
import { MerchantBackend } from "../../../../declaration";
import { Translate, useTranslator } from "../../../../i18n";
@@ -52,6 +53,35 @@ type Unpaid = MerchantBackend.Orders.CheckPaymentUnpaidResponse
type Claimed = MerchantBackend.Orders.CheckPaymentClaimedResponse
+function ContractTerms({ value }: { value: CT }) {
+ const i18n = useTranslator()
+
+ return <InputGroup name="contract_terms" label={i18n`Contract Terms`}>
+ <FormProvider<CT> object={value} valueHandler={null} >
+ <Input<CT> readonly name="summary" label={i18n`Summary`} tooltip={i18n`human-readable description of the whole purchase`} />
+ <InputCurrency<CT> readonly name="amount" label={i18n`Amount`} tooltip={i18n`total price for the transaction`} />
+ {value.fulfillment_url &&
+ <Input<CT> readonly name="fulfillment_url" label={i18n`Fulfillment URL`} tooltip={i18n`URL for this purchase`} />
+ }
+ <Input<CT> readonly name="max_fee" label={i18n`Max fee`} tooltip={i18n`maximum total deposit fee accepted by the merchant for this contract`} />
+ <Input<CT> readonly name="max_wire_fee" label={i18n`Max wire fee`} tooltip={i18n`maximum wire fee accepted by the merchant`} />
+ <Input<CT> readonly name="wire_fee_amortization" label={i18n`Wire fee amortization`} tooltip={i18n`over how many customer transactions does the merchant expect to amortize wire fees on average`} />
+ <InputDate<CT> readonly name="timestamp" label={i18n`Created at`} tooltip={i18n`time when this contract was generated`} />
+ <InputDate<CT> readonly name="refund_deadline" label={i18n`Refund deadline`} tooltip={i18n`after this deadline has passed no refunds will be accepted`} />
+ <InputDate<CT> readonly name="pay_deadline" label={i18n`Payment deadline`} tooltip={i18n`after this deadline, the merchant won't accept payments for the contract`} />
+ <InputDate<CT> readonly name="wire_transfer_deadline" label={i18n`Wire transfer deadline`} tooltip={i18n`transfer deadline for the exchange`} />
+ <InputDate<CT> readonly name="delivery_date" label={i18n`Delivery date`} tooltip={i18n`time indicating when the order should be delivered`} />
+ {value.delivery_date &&
+ <InputGroup name="delivery_location" label={i18n`Location`} tooltip={i18n`where the order will be delivered`} >
+ <InputLocation name="payments.delivery_location" />
+ </InputGroup>
+ }
+ <InputDuration<CT> readonly name="auto_refund" label={i18n`Auto-refund delay`} tooltip={i18n`how long the wallet should try to get an automatic refund for the purchase`} />
+ <Input<CT> readonly name="extra" label={i18n`Extra info`} tooltip={i18n`extra data that is only interpreted by the merchant frontend`} />
+ </FormProvider>
+ </InputGroup>
+}
+
function ClaimedPage({ id, order }: { id: string; order: MerchantBackend.Orders.CheckPaymentClaimedResponse }) {
const events: Event[] = []
events.push({
@@ -126,11 +156,8 @@ function ClaimedPage({ id, order }: { id: string; order: MerchantBackend.Orders.
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
- // maxWidth: '100%',
}}>
- {/* <a href={order.order_status_url} rel="nofollow" target="new">{order.order_status_url}</a> */}
- <p><Translate>pay at</Translate>: <b>missing value, there is no order_status_url</b></p>
- <p><Translate>created at</Translate>: {format(new Date(order.contract_terms.timestamp.t_ms), 'yyyy-MM-dd HH:mm:ss')}</p>
+ <p><b><Translate>claimed at</Translate>:</b> {format(new Date(order.contract_terms.timestamp.t_ms), 'yyyy-MM-dd HH:mm:ss')}</p>
</div>
</div>
</div>
@@ -155,18 +182,12 @@ function ClaimedPage({ id, order }: { id: string; order: MerchantBackend.Orders.
</div>
</section>
- {order.contract_terms.products.length > 0 &&
- <section class="section">
- <div class="columns">
- <div class="column is-12" >
- <div class="title"><Translate>Product list</Translate></div>
- <ProductList list={order.contract_terms.products} />
- </div>
- <div class="column" />
- </div>
- </section>
- }
+ {order.contract_terms.products.length ? <Fragment>
+ <div class="title"><Translate>Product list</Translate></div>
+ <ProductList list={order.contract_terms.products} />
+ </Fragment> : undefined}
+ {value.contract_terms && <ContractTerms value={value.contract_terms} />}
</div>
<div class="column" />
</div>
@@ -207,19 +228,43 @@ function PaidPage({ id, order, onRefund }: { id: string; order: MerchantBackend.
type: 'refund',
})
})
- order.wire_details.forEach(e => {
- events.push({
- when: new Date(e.execution_time.t_ms),
- description: `wired`,
- type: 'wired',
- })
- })
- if (order.contract_terms.wire_transfer_deadline.t_ms !== 'never' &&
- order.contract_terms.wire_transfer_deadline.t_ms < new Date().getTime()) events.push({
- when: new Date(order.contract_terms.wire_transfer_deadline.t_ms - 1000 * 10),
- description: `wired (faked)`,
- type: 'wired',
- })
+ if (order.wire_details && order.wire_details.length) {
+ if (order.wire_details.length > 1) {
+ let last: MerchantBackend.Orders.TransactionWireTransfer | null = null
+ let first: MerchantBackend.Orders.TransactionWireTransfer | null = null
+ let total: AmountJson | null = null
+
+ order.wire_details.forEach(w => {
+ if (last === null || last.execution_time.t_ms < w.execution_time.t_ms) {
+ last = w
+ }
+ if (first === null || first.execution_time.t_ms > w.execution_time.t_ms) {
+ first = w
+ }
+ total = total === null ? Amounts.parseOrThrow(w.amount) : Amounts.add(total, Amounts.parseOrThrow(w.amount)).amount
+ })
+ events.push({
+ when: new Date(last!.execution_time.t_ms),
+ description: `wired ${Amounts.stringify(total!)}`,
+ type: 'wired-range',
+ })
+ events.push({
+ when: new Date(first!.execution_time.t_ms),
+ description: `wire transfer started...`,
+ type: 'wired-range',
+ })
+ } else {
+ order.wire_details.forEach(e => {
+ events.push({
+ when: new Date(e.execution_time.t_ms),
+ description: `wired ${e.amount}`,
+ type: 'wired',
+ })
+ })
+
+ }
+
+ }
const [value, valueHandler] = useState<Partial<Paid>>(order)
@@ -261,10 +306,9 @@ function PaidPage({ id, order, onRefund }: { id: string; order: MerchantBackend.
<div class="level-item">
<h1 class="title">
<div class="buttons">
- {refundable && <button class="button is-danger" onClick={() => onRefund(id)}><Translate>refund</Translate></button>}
- <button class="button is-info" onClick={() => {
- if (order.contract_terms.fulfillment_url) copyToClipboard(order.contract_terms.fulfillment_url)
- }}><Translate>copy url</Translate></button>
+ <span class="has-tooltip-left" data-tooltip={refundable ? i18n`refund order`: i18n`not refundable`}>
+ <button class="button is-danger" disabled={!refundable} onClick={() => onRefund(id)}><Translate>refund</Translate></button>
+ </span>
</div>
</h1>
</div>
@@ -301,53 +345,23 @@ function PaidPage({ id, order, onRefund }: { id: string; order: MerchantBackend.
<InputCurrency<Paid> name="deposit_total" readonly label={i18n`Deposit total`} />
{order.refunded && <InputCurrency<Paid> name="refund_amount" readonly label={i18n`Refunded amount`} />}
<Input<Paid> name="order_status" readonly label={i18n`Order status`} />
- {order.order_status_url && <Input<Paid> name="order_status_url" readonly label={i18n`Status URL`} />}
+ <TextField<Paid> name="order_status_url" label={i18n`Status URL`} >
+ <a target="_blank" href={order.order_status_url}>
+ {order.order_status_url}
+ </a>
+ </TextField>
</FormProvider>
</div>
</div>
</section>
- {value.contract_terms && <section class="section">
- <div class="columns">
- <div class="column is-12" >
- <div class="title"><Translate>Contract Terms</Translate></div>
- <FormProvider<CT> object={value.contract_terms} valueHandler={null} >
- <Input<CT> readonly name="summary" label={i18n`Summary`} tooltip={i18n`human-readable description of the whole purchase`} />
- <InputCurrency<CT> readonly name="amount" label={i18n`Amount`} tooltip={i18n`total price for the transaction`} />
- {value.contract_terms.fulfillment_url &&
- <Input<CT> readonly name="fulfillment_url" label={i18n`Fulfillment URL`} tooltip={i18n`URL for this purchase`} />
- }
- <Input<CT> readonly name="max_fee" label={i18n`Max fee`} tooltip={i18n`maximum total deposit fee accepted by the merchant for this contract`} />
- <Input<CT> readonly name="max_wire_fee" label={i18n`Max wire fee`} tooltip={i18n`maximum wire fee accepted by the merchant`} />
- <Input<CT> readonly name="wire_fee_amortization" label={i18n`Wire fee amortization`} tooltip={i18n`over how many customer transactions does the merchant expect to amortize wire fees on average`} />
- <InputDate<CT> readonly name="timestamp" label={i18n`Created at`} tooltip={i18n`time when this contract was generated`} />
- <InputDate<CT> readonly name="refund_deadline" label={i18n`Refund deadline`} tooltip={i18n`after this deadline has passed no refunds will be accepted`} />
- <InputDate<CT> readonly name="pay_deadline" label={i18n`Payment deadline`} tooltip={i18n`after this deadline, the merchant won't accept payments for the contract`} />
- <InputDate<CT> readonly name="wire_transfer_deadline" label={i18n`Wire transfer deadline`} tooltip={i18n`transfer deadline for the exchange`} />
- <InputDate<CT> readonly name="delivery_date" label={i18n`Delivery date`} tooltip={i18n`time indicating when the order should be delivered`} />
- {value.contract_terms.delivery_date &&
- <InputGroup name="delivery_location" label={i18n`Location`} tooltip={i18n`where the order will be delivered`} >
- <InputLocation name="payments.delivery_location" />
- </InputGroup>
- }
- <InputDuration<CT> readonly name="auto_refund" label={i18n`Auto-refund delay`} tooltip={i18n`how long the wallet should try to get an automatic refund for the purchase`} />
- <Input<CT> readonly name="extra" label={i18n`Extra info`} tooltip={i18n`extra data that is only interpreted by the merchant frontend`} />
- </FormProvider>
- </div>
- <div class="column" />
- </div>
- </section>}
- {order.contract_terms.products.length ? <section class="section">
- <div class="columns">
- <div class="column is-12" >
- <div class="title"><Translate>Product list</Translate></div>
- <ProductList list={order.contract_terms.products} />
- </div>
- <div class="column" />
- </div>
- </section> : undefined}
+ {order.contract_terms.products.length ? <Fragment>
+ <div class="title"><Translate>Product list</Translate></div>
+ <ProductList list={order.contract_terms.products} />
+ </Fragment> : undefined}
+ {value.contract_terms && <ContractTerms value={value.contract_terms} />}
</div>
<div class="column" />
</div>
@@ -380,10 +394,9 @@ function UnpaidPage({ id, order }: { id: string; order: MerchantBackend.Orders.C
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
- // maxWidth: '100%',
}}>
- <p><Translate>pay at</Translate>: <a href={order.order_status_url} rel="nofollow" target="new">{order.order_status_url}</a></p>
- <p><Translate>created at</Translate>: <b>missing value, there is no contract term yet</b></p>
+ <p><b><Translate>pay at</Translate>:</b> <a href={order.order_status_url} rel="nofollow" target="new">{order.order_status_url}</a></p>
+ <p><b><Translate>created at</Translate>:</b> {format(new Date(order.creation_time.t_ms), 'yyyy-MM-dd HH:mm:ss')}</p>
</div>
</div>
</div>
@@ -394,8 +407,10 @@ function UnpaidPage({ id, order }: { id: string; order: MerchantBackend.Orders.C
<section class="section is-main-section">
<div class="columns">
<div class="column" />
- <div class="column is-6">
+ <div class="column is-four-fifths">
<FormProvider<Unpaid> object={value} valueHandler={valueHandler} >
+ <Input<Unpaid> readonly name="summary" label={i18n`Summary`} tooltip={i18n`human-readable description of the whole purchase`} />
+ <InputCurrency<Unpaid> readonly name="total_amount" label={i18n`Amount`} tooltip={i18n`total price for the transaction`} />
<Input<Unpaid> name="order_status" readonly label={i18n`Order status`} />
<Input<Unpaid> name="order_status_url" readonly label={i18n`Order status URL`} />
<Input<Unpaid> name="taler_pay_uri" readonly label={i18n`Payment URI`} />
@@ -432,7 +447,7 @@ export function DetailPage({ id, selected, onRefund, onBack }: Props): VNode {
/>}
<div class="columns">
<div class="column" />
- <div class="column is-two-thirds">
+ <div class="column is-four-fifths">
<div class="buttons is-right mt-5">
<button class="button" onClick={onBack}><Translate>Back</Translate></button>
</div>
diff --git a/packages/frontend/src/paths/instance/orders/details/Timeline.tsx b/packages/frontend/src/paths/instance/orders/details/Timeline.tsx
index d4f17c4..16adbcb 100644
--- a/packages/frontend/src/paths/instance/orders/details/Timeline.tsx
+++ b/packages/frontend/src/paths/instance/orders/details/Timeline.tsx
@@ -47,7 +47,7 @@ export function Timeline({ events:e }: Props) {
}
})
return <div class="timeline">
- {events.map((e,i) => {
+ {state.map((e,i) => {
return <div key={i} class="timeline-item">
{(() => {
switch (e.type) {
@@ -55,6 +55,7 @@ export function Timeline({ events:e }: Props) {
case "delivery": return <div class="timeline-marker is-icon "><i class="mdi mdi-delivery" /></div>
case "start": return <div class="timeline-marker is-icon is-success"><i class="mdi mdi-flag " /></div>
case "wired": return <div class="timeline-marker is-icon is-success"><i class="mdi mdi-cash" /></div>
+ case "wired-range": return <div class="timeline-marker is-icon is-success"><i class="mdi mdi-cash" /></div>
case "refund": return <div class="timeline-marker is-icon is-danger"><i class="mdi mdi-cash" /></div>
case "now": return <div class="timeline-marker is-icon is-info"><i class="mdi mdi-clock" /></div>
}
@@ -71,5 +72,5 @@ export function Timeline({ events:e }: Props) {
export interface Event {
when: Date;
description: string;
- type: 'start' | 'refund' | 'wired' | 'deadline' | 'delivery' | 'now'
+ type: 'start' | 'refund' | 'wired' | 'wired-range' |'deadline' | 'delivery' | 'now'
}
diff --git a/packages/frontend/src/paths/instance/orders/list/Table.tsx b/packages/frontend/src/paths/instance/orders/list/Table.tsx
index 4057ca2..41c7293 100644
--- a/packages/frontend/src/paths/instance/orders/list/Table.tsx
+++ b/packages/frontend/src/paths/instance/orders/list/Table.tsx
@@ -31,7 +31,7 @@ import { ConfirmModal } from "../../../../components/modal";
import { MerchantBackend, WithId } from "../../../../declaration";
import { useOrderDetails } from "../../../../hooks/order";
import { Translate, useTranslator } from "../../../../i18n";
-import { AuthorizeTipSchema, RefundSchema as RefundSchema } from "../../../../schemas";
+import { RefundSchema as RefundSchema } from "../../../../schemas";
import { mergeRefunds, subtractPrices, sumPrices } from "../../../../utils/amount";
import { AMOUNT_ZERO_REGEX } from "../../../../utils/constants";
@@ -54,6 +54,7 @@ export function CardTable({ instances, onCreate, onRefund, onCopyURL, onSelect,
const [showRefund, setShowRefund] = useState<string | undefined>(undefined)
+ const i18n = useTranslator()
return <div class="card has-table">
<header class="card-header">
@@ -62,9 +63,11 @@ export function CardTable({ instances, onCreate, onRefund, onCopyURL, onSelect,
<div class="card-header-icon" aria-label="more options" />
<div class="card-header-icon" aria-label="more options">
+ <span class="has-tooltip-left" data-tooltip={i18n`add new order`}>
<button class="button is-info" type="button" onClick={onCreate}>
<span class="icon is-small" ><i class="mdi mdi-plus mdi-36px" /></span>
</button>
+ </span>
</div>
</header>
@@ -114,7 +117,7 @@ function Table({ instances, onSelect, onRefund, onCopyURL, onLoadMoreAfter, onLo
<tr>
<th style={{ minWidth: 100 }}><Translate>Date</Translate></th>
<th style={{ minWidth: 100 }}><Translate>Amount</Translate></th>
- <th style={{ minWidth: 500 }}><Translate>Summary</Translate></th>
+ <th style={{ minWidth: 400 }}><Translate>Summary</Translate></th>
<th style={{ minWidth: 50 }} />
</tr>
</thead>
diff --git a/packages/frontend/src/paths/instance/orders/list/index.tsx b/packages/frontend/src/paths/instance/orders/list/index.tsx
index c85db0b..8bfe23d 100644
--- a/packages/frontend/src/paths/instance/orders/list/index.tsx
+++ b/packages/frontend/src/paths/instance/orders/list/index.tsx
@@ -41,7 +41,7 @@ interface Props {
export default function ({ onUnauthorized, onLoadError, onCreate, onSelect, onNotFound }: Props): VNode {
- const [filter, setFilter] = useState<InstanceOrderFilter>({ })
+ const [filter, setFilter] = useState<InstanceOrderFilter>({})
const [pickDate, setPickDate] = useState(false)
const setNewDate = (date: Date) => setFilter(prev => ({ ...prev, date }))
@@ -66,7 +66,7 @@ export default function ({ onUnauthorized, onLoadError, onCreate, onSelect, onNo
async function testIfOrderExistAndSelect() {
if (!orderId) {
- setErrorOrderId('Enter an order id')
+ setErrorOrderId(i18n`Enter an order id`)
return;
}
try {
@@ -74,11 +74,12 @@ export default function ({ onUnauthorized, onLoadError, onCreate, onSelect, onNo
onSelect(orderId)
setErrorOrderId(undefined)
} catch {
- setErrorOrderId('order not found')
+ setErrorOrderId(i18n`order not found`)
}
}
const i18n = useTranslator()
+ const dateTooltip = i18n`jump to order closer to a given date`
return <section class="section is-main-section">
<NotificationCard notification={notif} />
@@ -91,11 +92,11 @@ export default function ({ onUnauthorized, onLoadError, onCreate, onSelect, onNo
<input class={errorOrderId ? "input is-danger" : "input"} type="text" value={orderId} onChange={e => setOrderId(e.currentTarget.value)} placeholder={i18n`order id`} />
{errorOrderId && <p class="help is-danger">{errorOrderId}</p>}
</div>
- <div class="control">
- <a class="button" onClick={testIfOrderExistAndSelect}>
+ <span class="has-tooltip-bottom" data-tooltip={i18n`view order details`}>
+ <button class="button" onClick={testIfOrderExistAndSelect}>
<span class="icon"><i class="mdi mdi-arrow-right" /></span>
- </a>
- </div>
+ </button>
+ </span>
</div>
</div>
</div>
@@ -104,10 +105,26 @@ export default function ({ onUnauthorized, onLoadError, onCreate, onSelect, onNo
<div class="column">
<div class="tabs">
<ul>
- <li class={isAllActive}><a onClick={() => setFilter({})}><Translate>All</Translate></a></li>
- <li class={isPaidActive}><a onClick={() => setFilter({ paid: 'yes' })}><Translate>Paid</Translate></a></li>
- <li class={isRefundedActive}><a onClick={() => setFilter({ refunded: 'yes' })}><Translate>Refunded</Translate></a></li>
- <li class={isNotWiredActive}><a onClick={() => setFilter({ wired: 'no' })}><Translate>Not wired</Translate></a></li>
+ <li class={isAllActive}>
+ <div class="has-tooltip-right" data-tooltip={i18n`remove all filters`}>
+ <a onClick={() => setFilter({})}><Translate>All</Translate></a>
+ </div>
+ </li>
+ <li class={isPaidActive}>
+ <div class="has-tooltip-right" data-tooltip={i18n`filter paid orders`}>
+ <a onClick={() => setFilter({ paid: 'yes' })}><Translate>Paid</Translate></a>
+ </div>
+ </li>
+ <li class={isRefundedActive}>
+ <div class="has-tooltip-right" data-tooltip={i18n`filter refunded orders`}>
+ <a onClick={() => setFilter({ refunded: 'yes' })}><Translate>Refunded</Translate></a>
+ </div>
+ </li>
+ <li class={isNotWiredActive}>
+ <div class="has-tooltip-left" data-tooltip={i18n`filter not yet wired orders`}>
+ <a onClick={() => setFilter({ wired: 'no' })}><Translate>Not wired</Translate></a>
+ </div>
+ </li>
</ul>
</div>
</div>
@@ -120,12 +137,16 @@ export default function ({ onUnauthorized, onLoadError, onCreate, onSelect, onNo
</a>
</div>}
<div class="control">
- <input class="input" type="text" readonly value={!filter.date ? '' : format(filter.date, 'yyyy/MM/dd')} placeholder={i18n`date (YYYY/MM/DD)`} />
+ <span class="has-tooltip-top" data-tooltip={dateTooltip}>
+ <input class="input" type="text" readonly value={!filter.date ? '' : format(filter.date, 'yyyy/MM/dd')} placeholder={i18n`date (YYYY/MM/DD)`} onClick={() => { setPickDate(true) }} />
+ </span>
</div>
<div class="control">
- <a class="button" onClick={() => { setPickDate(true) }}>
- <span class="icon"><i class="mdi mdi-calendar" /></span>
- </a>
+ <span class="has-tooltip-left" data-tooltip={dateTooltip}>
+ <a class="button" onClick={() => { setPickDate(true) }}>
+ <span class="icon"><i class="mdi mdi-calendar" /></span>
+ </a>
+ </span>
</div>
</div>
</div>
diff --git a/packages/frontend/src/paths/instance/products/create/CreatePage.tsx b/packages/frontend/src/paths/instance/products/create/CreatePage.tsx
index e6e6f1e..78c0f0d 100644
--- a/packages/frontend/src/paths/instance/products/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/instance/products/create/CreatePage.tsx
@@ -45,7 +45,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
<section class="section is-main-section">
<div class="columns">
<div class="column" />
- <div class="column is-two-thirds">
+ <div class="column is-four-fifths">
<ProductForm onSubscribe={addFormSubmitter} />
<div class="buttons is-right mt-5">
diff --git a/packages/frontend/src/paths/instance/products/create/CreatedSuccessfully.tsx b/packages/frontend/src/paths/instance/products/create/CreatedSuccessfully.tsx
index dc39d84..b567504 100644
--- a/packages/frontend/src/paths/instance/products/create/CreatedSuccessfully.tsx
+++ b/packages/frontend/src/paths/instance/products/create/CreatedSuccessfully.tsx
@@ -16,6 +16,7 @@
import { h, VNode } from "preact";
import { CreatedSuccessfully as Template } from "../../../../components/notifications/CreatedSuccessfully";
import { Entity } from "./index";
+import emptyImage from "../../assets/empty.png";
interface Props {
entity: Entity;
diff --git a/packages/frontend/src/paths/instance/products/list/Table.tsx b/packages/frontend/src/paths/instance/products/list/Table.tsx
index 878506d..1b4a7b3 100644
--- a/packages/frontend/src/paths/instance/products/list/Table.tsx
+++ b/packages/frontend/src/paths/instance/products/list/Table.tsx
@@ -28,6 +28,7 @@ import { InputNumber } from "../../../../components/form/InputNumber"
import { MerchantBackend, WithId } from "../../../../declaration"
import emptyImage from "../../../../assets/empty.png";
import { Translate, useTranslator } from "../../../../i18n"
+import { Amounts } from "@gnu-taler/taler-util"
type Entity = MerchantBackend.Products.ProductDetail & WithId
@@ -35,23 +36,23 @@ interface Props {
instances: Entity[];
onDelete: (id: Entity) => void;
onSelect: (product: Entity) => void;
- onUpdate: (id: string, data: MerchantBackend.Products.ProductPatchDetail) => void;
+ onUpdate: (id: string, data: MerchantBackend.Products.ProductPatchDetail) => Promise<void>;
onCreate: () => void;
selected?: boolean;
}
export function CardTable({ instances, onCreate, onSelect, onUpdate, onDelete }: Props): VNode {
const [rowSelection, rowSelectionHandler] = useState<string | undefined>(undefined)
-
+ const i18n = useTranslator()
return <div class="card has-table">
<header class="card-header">
<p class="card-header-title"><span class="icon"><i class="mdi mdi-shopping" /></span><Translate>Products</Translate></p>
-
- <div class="card-header-icon" aria-label="more options" />
<div class="card-header-icon" aria-label="more options">
- <button class="button is-info" type="button" onClick={onCreate}>
- <span class="icon is-small" ><i class="mdi mdi-plus mdi-36px" /></span>
- </button>
+ <span class="has-tooltip-left" data-tooltip={i18n`add new product`}>
+ <button class="button is-info" type="button" onClick={onCreate}>
+ <span class="icon is-small" ><i class="mdi mdi-plus mdi-36px" /></span>
+ </button>
+ </span>
</div>
</header>
@@ -71,12 +72,13 @@ interface TableProps {
rowSelection: string | undefined;
instances: Entity[];
onSelect: (id: Entity) => void;
- onUpdate: (id: string, data: MerchantBackend.Products.ProductPatchDetail) => void;
+ onUpdate: (id: string, data: MerchantBackend.Products.ProductPatchDetail) => Promise<void>;
onDelete: (id: Entity) => void;
rowSelectionHandler: StateUpdater<string | undefined>;
}
function Table({ rowSelection, rowSelectionHandler, instances, onSelect, onUpdate, onDelete }: TableProps): VNode {
+ const i18n = useTranslator()
return (
<div class="table-container">
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
@@ -108,30 +110,38 @@ function Table({ rowSelection, rowSelectionHandler, instances, onSelect, onUpdat
stockInfo = <label title={restStockInfo}>{totalStock} {i.unit}</label>
}
+ const isFree = Amounts.parseOrThrow(i.price).value === 0
+
return <Fragment key={i.id}><tr key="info">
<td onClick={() => rowSelection !== i.id && rowSelectionHandler(i.id)} style={{ cursor: 'pointer' }} >
<img src={i.image ? i.image : emptyImage} style={{ border: 'solid black 1px', width: 100, height: 100 }} />
</td>
<td onClick={() => rowSelection !== i.id && rowSelectionHandler(i.id)} style={{ cursor: 'pointer' }} >{i.description}</td>
- <td onClick={() => rowSelection !== i.id && rowSelectionHandler(i.id)} style={{ cursor: 'pointer' }} >{i.price} / {i.unit}</td>
+ <td onClick={() => rowSelection !== i.id && rowSelectionHandler(i.id)} style={{ cursor: 'pointer' }} >
+ {isFree ? i18n`free` : `${i.price} / ${i.unit}`}
+ </td>
<td onClick={() => rowSelection !== i.id && rowSelectionHandler(i.id)} style={{ cursor: 'pointer' }} >{sum(i.taxes)}</td>
<td onClick={() => rowSelection !== i.id && rowSelectionHandler(i.id)} style={{ cursor: 'pointer' }} >{difference(i.price, sum(i.taxes))}</td>
<td onClick={() => rowSelection !== i.id && rowSelectionHandler(i.id)} style={{ cursor: 'pointer' }} >{stockInfo}</td>
<td onClick={() => rowSelection !== i.id && rowSelectionHandler(i.id)} style={{ cursor: 'pointer' }} >{i.total_sold} {i.unit}</td>
<td class="is-actions-cell right-sticky">
<div class="buttons is-right">
- <button class="button is-small is-success jb-modal" type="button" onClick={(): void => onSelect(i)}>
- Update
- </button>
- <button class="button is-small is-danger jb-modal" type="button" onClick={(): void => onDelete(i)}>
- Delete
- </button>
+ <span class="has-tooltip-bottom" data-tooltip={i18n`go to product update page`}>
+ <button class="button is-small is-success " type="button" onClick={(): void => onSelect(i)}>
+ <Translate>Update</Translate>
+ </button>
+ </span>
+ <span class="has-tooltip-left" data-tooltip={i18n`remove this product from the database`}>
+ <button class="button is-small is-danger" type="button" onClick={(): void => onDelete(i)}>
+ <Translate>Delete</Translate>
+ </button>
+ </span>
</div>
</td>
</tr>
{rowSelection === i.id && <tr key="form">
<td colSpan={10} >
- <FastProductUpdateForm product={i} onUpdate={(prod) => onUpdate(i.id, prod)} onCancel={() => rowSelectionHandler(undefined)} />
+ <FastProductUpdateForm product={i} onUpdate={(prod) => onUpdate(i.id, prod).then(r => rowSelectionHandler(undefined))} onCancel={() => rowSelectionHandler(undefined)} />
</td>
</tr>}
</Fragment>
@@ -144,7 +154,7 @@ function Table({ rowSelection, rowSelectionHandler, instances, onSelect, onUpdat
interface FastProductUpdateFormProps {
product: Entity;
- onUpdate: (data: MerchantBackend.Products.ProductPatchDetail) => void;
+ onUpdate: (data: MerchantBackend.Products.ProductPatchDetail) => Promise<void>;
onCancel: () => void;
}
interface FastProductUpdate {
@@ -152,9 +162,12 @@ interface FastProductUpdate {
lost: number;
price: string;
}
+interface UpdatePrice {
+ price: string;
+}
function FastProductWithInfiniteStockUpdateForm({ product, onUpdate, onCancel }: FastProductUpdateFormProps) {
- const [value, valueHandler] = useState<{ price: string }>({ price: product.price })
+ const [value, valueHandler] = useState<UpdatePrice>({ price: product.price })
const i18n = useTranslator()
return <Fragment>
@@ -164,14 +177,12 @@ function FastProductWithInfiniteStockUpdateForm({ product, onUpdate, onCancel }:
<div class="buttons is-right mt-5">
<button class="button" onClick={onCancel} ><Translate>Cancel</Translate></button>
- <button class="button is-info" onClick={() => {
-
- return onUpdate({
+ <span class="has-tooltip-left" data-tooltip={i18n`update product with new price`}>
+ <button class="button is-info" onClick={() => onUpdate({
...product,
price: value.price,
- })
-
- }}><Translate>Confirm</Translate></button>
+ })}><Translate>Confirm</Translate></button>
+ </span>
</div>
</Fragment>
@@ -190,12 +201,6 @@ function FastProductWithManagedStockUpdateForm({ product, onUpdate, onCancel }:
: undefined
}
- const stockUpdateDescription = errors.lost ? '' : (
- !!value.incoming || !!value.lost ?
- `current stock will change from ${currentStock} to ${currentStock + value.incoming - value.lost}` :
- `current stock will stay at ${currentStock}`
- )
-
const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== undefined)
const i18n = useTranslator()
@@ -203,29 +208,20 @@ function FastProductWithManagedStockUpdateForm({ product, onUpdate, onCancel }:
<FormProvider<FastProductUpdate> name="added" errors={errors} object={value} valueHandler={valueHandler as any} >
<InputNumber<FastProductUpdate> name="incoming" label={i18n`Incoming`} tooltip={i18n`add more elements to the inventory`} />
<InputNumber<FastProductUpdate> name="lost" label={i18n`Lost`} tooltip={i18n`report elements lost in the inventory`} />
- <div class="field is-horizontal">
- <div class="field-label is-normal" />
- <div class="field-body is-flex-grow-3">
- <div class="field">
- {stockUpdateDescription}
- </div>
- </div>
- </div>
<InputCurrency<FastProductUpdate> name="price" label={i18n`Price`} tooltip={i18n`new price for the product`} />
</FormProvider>
<div class="buttons is-right mt-5">
<button class="button" onClick={onCancel} ><Translate>Cancel</Translate></button>
- <button class="button is-info" disabled={hasErrors} onClick={() => {
-
- return onUpdate({
+ <span class="has-tooltip-left" data-tooltip={hasErrors ? i18n`the are value with errors` : i18n`update product with new stock and price`}>
+ <button class="button is-info" disabled={hasErrors} onClick={() => onUpdate({
...product,
total_stock: product.total_stock + value.incoming,
total_lost: product.total_lost + value.lost,
price: value.price,
})
-
- }}><Translate>Confirm</Translate></button>
+ }><Translate>Confirm</Translate></button>
+ </span>
</div>
</Fragment>
diff --git a/packages/frontend/src/paths/instance/products/update/UpdatePage.tsx b/packages/frontend/src/paths/instance/products/update/UpdatePage.tsx
index e0a2b16..1dfca99 100644
--- a/packages/frontend/src/paths/instance/products/update/UpdatePage.tsx
+++ b/packages/frontend/src/paths/instance/products/update/UpdatePage.tsx
@@ -26,7 +26,7 @@ import { MerchantBackend, WithId } from "../../../../declaration";
import { useListener } from "../../../../hooks";
import { Translate } from "../../../../i18n";
-type Entity = MerchantBackend.Products.ProductDetail & { product_id: string}
+type Entity = MerchantBackend.Products.ProductDetail & { product_id: string }
interface Props {
onUpdate: (d: Entity) => Promise<void>;
@@ -41,17 +41,30 @@ export function UpdatePage({ product, onUpdate, onBack }: Props): VNode {
})
return <div>
- <section class="section is-main-section">
+ <section class="section">
+ <section class="hero is-hero-bar">
+ <div class="hero-body">
+
+ <div class="level">
+ <div class="level-left">
+ <div class="level-item">
+ <span class="is-size-4"><Translate>Product id:</Translate><b>{product.product_id}</b></span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </section>
+ <hr />
+
<div class="columns">
<div class="column" />
- <div class="column is-two-thirds">
+ <div class="column is-four-fifths">
<ProductForm initial={product} onSubscribe={addFormSubmitter} alreadyExist />
<div class="buttons is-right mt-5">
{onBack && <button class="button" onClick={onBack} ><Translate>Cancel</Translate></button>}
<AsyncButton onClick={submitForm} disabled={!submitForm}><Translate>Confirm</Translate></AsyncButton>
</div>
-
</div>
<div class="column" />
</div>
diff --git a/packages/frontend/src/paths/instance/reserves/create/CreatePage.tsx b/packages/frontend/src/paths/instance/reserves/create/CreatePage.tsx
index 8669242..cdaf475 100644
--- a/packages/frontend/src/paths/instance/reserves/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/instance/reserves/create/CreatePage.tsx
@@ -133,7 +133,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
<section class="section is-main-section">
<div class="columns">
<div class="column" />
- <div class="column is-two-thirds">
+ <div class="column is-four-fifths">
<div class="tabs is-toggle is-fullwidth is-small">
<ul>
diff --git a/packages/frontend/src/paths/instance/reserves/details/DetailPage.tsx b/packages/frontend/src/paths/instance/reserves/details/DetailPage.tsx
index 08942f6..06fbf20 100644
--- a/packages/frontend/src/paths/instance/reserves/details/DetailPage.tsx
+++ b/packages/frontend/src/paths/instance/reserves/details/DetailPage.tsx
@@ -20,22 +20,16 @@
*/
import { Amounts } from "@gnu-taler/taler-util";
-import { format, isAfter } from "date-fns";
+import { format } from "date-fns";
import { Fragment, h, VNode } from "preact";
-import { useState } from "preact/hooks";
import { FormProvider } from "../../../../components/form/FormProvider";
import { Input } from "../../../../components/form/Input";
import { InputCurrency } from "../../../../components/form/InputCurrency";
import { InputDate } from "../../../../components/form/InputDate";
-import { InputDuration } from "../../../../components/form/InputDuration";
-import { InputGroup } from "../../../../components/form/InputGroup";
-import { InputLocation } from "../../../../components/form/InputLocation";
-import { NotificationCard } from "../../../../components/menu";
-import { ProductList } from "../../../../components/product/ProductList";
+import { TextField } from "../../../../components/form/TextField";
import { MerchantBackend } from "../../../../declaration";
import { useTipDetails } from "../../../../hooks/tips";
import { Translate, useTranslator } from "../../../../i18n";
-import { mergeRefunds } from "../../../../utils/amount";
type Entity = MerchantBackend.Tips.ReserveDetail;
type CT = MerchantBackend.ContractTerms
@@ -49,59 +43,60 @@ interface Props {
export function DetailPage({ id, selected, onBack }: Props): VNode {
const i18n = useTranslator()
const didExchangeAckTransfer = Amounts.isNonZero(Amounts.parseOrThrow(selected.exchange_initial_amount))
- return <div class="section main-section">
- <FormProvider object={{ ...selected, id }} valueHandler={null} >
- <InputDate<Entity> name="creation_time" label={i18n`Created at`} readonly />
- <InputDate<Entity> name="expiration_time" label={i18n`Valid until`} readonly />
- <InputCurrency<Entity> name="merchant_initial_amount" label={i18n`Created balance`} readonly />
- <Input<Entity> name="exchange_url" label={i18n`Exchange URL`} readonly />
-
- {didExchangeAckTransfer && <Fragment>
- <InputCurrency<Entity> name="exchange_initial_amount" label={i18n`Exchange balance`} readonly />
- <InputCurrency<Entity> name="pickup_amount" label={i18n`Picked up`} readonly />
- <InputCurrency<Entity> name="committed_amount" label={i18n`Committed`} readonly />
- </Fragment>
- }
- <Input<Entity> name="payto_uri" label={i18n`Account address`} readonly />
- <Input name="id" label={i18n`Subject`} readonly />
- </FormProvider>
-
- {didExchangeAckTransfer ? <Fragment>
- <div class="card has-table">
- <header class="card-header">
- <p class="card-header-title">
- <span class="icon"><i class="mdi mdi-cash-register" /></span>
- <Translate>Tips</Translate>
- </p>
-
- </header>
- <div class="card-content">
- <div class="b-table has-pagination">
- <div class="table-wrapper has-mobile-cards">
- {selected.tips && selected.tips.length > 0 ? <Table tips={selected.tips} /> : <EmptyTable />}
- </div></div>
- </div>
- </div>
- </Fragment> : <Fragment>
- <p class="is-size-5"><Translate>Now you should transfer to the exchange into the account address indicated above and the transaction must carry the subject message.</Translate></p>
-
- <p class="is-size-5"><Translate>For example :</Translate></p>
- <pre>
- {selected.payto_uri}?message={id}&amount={selected.merchant_initial_amount}
- </pre>
- </Fragment>
- }
-
- <div class="columns">
- <div class="column" />
- <div class="column is-two-thirds">
+ return <div class="columns">
+ <div class="column" />
+ <div class="column is-four-fifths">
+ <div class="section main-section">
+ <FormProvider object={{ ...selected, id }} valueHandler={null} >
+ <InputDate<Entity> name="creation_time" label={i18n`Created at`} readonly />
+ <InputDate<Entity> name="expiration_time" label={i18n`Valid until`} readonly />
+ <InputCurrency<Entity> name="merchant_initial_amount" label={i18n`Created balance`} readonly />
+ <TextField<Entity> name="exchange_url" label={i18n`Exchange URL`} readonly >
+ <a target="_blank" href={selected.exchange_url}>{selected.exchange_url}</a>
+ </TextField>
+
+ {didExchangeAckTransfer && <Fragment>
+ <InputCurrency<Entity> name="exchange_initial_amount" label={i18n`Exchange balance`} readonly />
+ <InputCurrency<Entity> name="pickup_amount" label={i18n`Picked up`} readonly />
+ <InputCurrency<Entity> name="committed_amount" label={i18n`Committed`} readonly />
+ </Fragment>
+ }
+ <Input<Entity> name="payto_uri" label={i18n`Account address`} readonly />
+ <Input name="id" label={i18n`Subject`} readonly />
+ </FormProvider>
+
+ {didExchangeAckTransfer ? <Fragment>
+ <div class="card has-table">
+ <header class="card-header">
+ <p class="card-header-title">
+ <span class="icon"><i class="mdi mdi-cash-register" /></span>
+ <Translate>Tips</Translate>
+ </p>
+
+ </header>
+ <div class="card-content">
+ <div class="b-table has-pagination">
+ <div class="table-wrapper has-mobile-cards">
+ {selected.tips && selected.tips.length > 0 ? <Table tips={selected.tips} /> : <EmptyTable />}
+ </div></div>
+ </div>
+ </div>
+ </Fragment> : <Fragment>
+ <p class="is-size-5"><Translate>Now you should transfer to the exchange into the account address indicated above and the transaction must carry the subject message.</Translate></p>
+ <p class="is-size-5"><Translate>For example :</Translate></p>
+ <pre>
+ {selected.payto_uri}?message={id}&amount={selected.merchant_initial_amount}
+ </pre>
+ </Fragment>
+ }
+
<div class="buttons is-right mt-5">
<button class="button" onClick={onBack}><Translate>Back</Translate></button>
</div>
+
</div>
- <div class="column" />
</div>
-
+ <div class="column" />
</div>
}
diff --git a/packages/frontend/src/paths/instance/reserves/list/Table.tsx b/packages/frontend/src/paths/instance/reserves/list/Table.tsx
index c53dd2a..6bca85b 100644
--- a/packages/frontend/src/paths/instance/reserves/list/Table.tsx
+++ b/packages/frontend/src/paths/instance/reserves/list/Table.tsx
@@ -22,7 +22,7 @@
import { format } from "date-fns"
import { Fragment, h, VNode } from "preact"
import { MerchantBackend, WithId } from "../../../../declaration"
-import { Translate } from "../../../../i18n"
+import { Translate, useTranslator } from "../../../../i18n"
type Entity = MerchantBackend.Tips.ReserveStatusEntry & WithId
@@ -47,6 +47,7 @@ export function CardTable({ instances, onCreate, onSelect, onNewTip, onDelete }:
return prev
}, new Array<Array<Entity>>([], []))
+ const i18n = useTranslator()
return <Fragment>
{withoutFunds.length > 0 && <div class="card has-table">
@@ -67,9 +68,12 @@ export function CardTable({ instances, onCreate, onSelect, onNewTip, onDelete }:
<p class="card-header-title"><span class="icon"><i class="mdi mdi-cash" /></span><Translate>Reserves ready</Translate></p>
<div class="card-header-icon" aria-label="more options" />
<div class="card-header-icon" aria-label="more options">
- <button class="button is-info" type="button" onClick={onCreate}>
- <span class="icon is-small" ><i class="mdi mdi-plus mdi-36px" /></span>
- </button>
+ <span class="has-tooltip-left" data-tooltip={i18n`add new reserve`}>
+
+ <button class="button is-info" type="button" onClick={onCreate}>
+ <span class="icon is-small" ><i class="mdi mdi-plus mdi-36px" /></span>
+ </button>
+ </span>
</div>
</header>
<div class="card-content">
diff --git a/packages/frontend/src/paths/instance/transfers/create/CreatePage.tsx b/packages/frontend/src/paths/instance/transfers/create/CreatePage.tsx
index 748722f..861268f 100644
--- a/packages/frontend/src/paths/instance/transfers/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/instance/transfers/create/CreatePage.tsx
@@ -77,7 +77,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
<section class="section is-main-section">
<div class="columns">
<div class="column" />
- <div class="column is-two-thirds">
+ <div class="column is-four-fifths">
<FormProvider object={state} valueHandler={setState} errors={errors}>
<Input<Entity> name="wtid" label={i18n`Transfer ID`} help="" tooltip={i18n`unique identifier of the wire transfer, usually 52 random characters long`} />
diff --git a/packages/frontend/src/paths/instance/transfers/list/Table.tsx b/packages/frontend/src/paths/instance/transfers/list/Table.tsx
index ad7248f..3794b01 100644
--- a/packages/frontend/src/paths/instance/transfers/list/Table.tsx
+++ b/packages/frontend/src/paths/instance/transfers/list/Table.tsx
@@ -59,6 +59,7 @@ export function CardTable({ instances, onCreate, onUpdate, onDelete, selected, o
}
}, [actionQueue, selected, onUpdate])
+ const i18n = useTranslator()
return <div class="card has-table">
<header class="card-header">
@@ -72,9 +73,11 @@ export function CardTable({ instances, onCreate, onUpdate, onDelete, selected, o
</button>
</div>
<div class="card-header-icon" aria-label="more options">
- <button class="button is-info" type="button" onClick={onCreate}>
- <span class="icon is-small" ><i class="mdi mdi-plus mdi-36px" /></span>
- </button>
+ <span class="has-tooltip-left" data-tooltip={i18n`add new transfer`}>
+ <button class="button is-info" type="button" onClick={onCreate}>
+ <span class="icon is-small" ><i class="mdi mdi-plus mdi-36px" /></span>
+ </button>
+ </span>
</div>
</header>
diff --git a/packages/frontend/src/paths/instance/transfers/list/index.tsx b/packages/frontend/src/paths/instance/transfers/list/index.tsx
index 5486451..5effb5f 100644
--- a/packages/frontend/src/paths/instance/transfers/list/index.tsx
+++ b/packages/frontend/src/paths/instance/transfers/list/index.tsx
@@ -74,7 +74,7 @@ export default function ListTransfer({ onUnauthorized, onLoadError, onCreate, on
return <section class="section is-main-section">
<div class="columns">
<div class="column" />
- <div class="column is-6">
+ <div class="column is-10">
<FormProvider object={form} valueHandler={setForm as any}>
<InputSelector name="payto_uri" label={i18n`Address`}
values={accounts}
@@ -87,9 +87,21 @@ export default function ListTransfer({ onUnauthorized, onLoadError, onCreate, on
</div>
<div class="tabs">
<ul>
- <li class={isAllTransfers}><a onClick={() => setFilter(undefined)}><Translate>All</Translate></a></li>
- <li class={isVerifiedTransfers}><a onClick={() => setFilter('yes')}><Translate>Verified</Translate></a></li>
- <li class={isNonVerifiedTransfers}><a onClick={() => setFilter('no')}><Translate>Non Verified</Translate></a></li>
+ <li class={isAllTransfers}>
+ <div class="has-tooltip-right" data-tooltip={i18n`remove all filters`}>
+ <a onClick={() => setFilter(undefined)}><Translate>All</Translate></a>
+ </div>
+ </li>
+ <li class={isVerifiedTransfers}>
+ <div class="has-tooltip-right" data-tooltip={i18n`remove all filters`}>
+ <a onClick={() => setFilter('yes')}><Translate>Verified</Translate></a>
+ </div>
+ </li>
+ <li class={isNonVerifiedTransfers}>
+ <div class="has-tooltip-right" data-tooltip={i18n`remove all filters`}>
+ <a onClick={() => setFilter('no')}><Translate>Non Verified</Translate></a>
+ </div>
+ </li>
</ul>
</div>
<View
diff --git a/packages/frontend/src/paths/instance/update/UpdatePage.tsx b/packages/frontend/src/paths/instance/update/UpdatePage.tsx
index 3fe17ff..5367e5d 100644
--- a/packages/frontend/src/paths/instance/update/UpdatePage.tsx
+++ b/packages/frontend/src/paths/instance/update/UpdatePage.tsx
@@ -36,6 +36,7 @@ import { useInstanceContext } from "../../../context/instance";
import { MerchantBackend } from "../../../declaration";
import { Translate, useTranslator } from "../../../i18n";
import { InstanceUpdateSchema as schema } from '../../../schemas';
+import { DefaultInstanceFormFields } from "../../../components/instance/DefaultInstanceFormFields";
type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage & { auth_token?: string }
@@ -101,8 +102,6 @@ export function UpdatePage({ onUpdate, onChangeAuth, selected, onBack }: Props):
}
const [active, setActive] = useState(false);
- const i18n = useTranslator()
-
return <div>
<section class="section">
@@ -112,7 +111,7 @@ export function UpdatePage({ onUpdate, onChangeAuth, selected, onBack }: Props):
<div class="level">
<div class="level-left">
<div class="level-item">
- <span class="is-size-4">Instance id: <b>{id}</b></span>
+ <span class="is-size-4"><Translate>Instance id</Translate>: <b>{id}</b></span>
</div>
</div>
<div class="level-right">
@@ -120,7 +119,7 @@ export function UpdatePage({ onUpdate, onChangeAuth, selected, onBack }: Props):
<h1 class="title">
<button class="button is-danger" onClick={(): void => { setActive(!active); }} >
<div class="icon is-left"><i class="mdi mdi-lock-reset" /></div>
- <span><Translate>Manage token</Translate></span>
+ <span><Translate>Manage access token</Translate></span>
</button>
</h1>
</div>
@@ -142,42 +141,25 @@ export function UpdatePage({ onUpdate, onChangeAuth, selected, onBack }: Props):
<div class="column" />
</div>
<hr />
+
<div class="columns">
<div class="column" />
<div class="column is-four-fifths">
<FormProvider<Entity> errors={errors} object={value} valueHandler={valueHandler} >
- <Input<Entity> name="name" label={i18n`Name`} tooltip={i18n`display name of this instance`} />
-
- <InputPayto<Entity> name="payto_uris" label={i18n`Account address`} help="x-taler-bank/bank.taler:5882/blogger" />
-
- <InputCurrency<Entity> name="default_max_deposit_fee" label={i18n`Default max deposit fee`} />
-
- <InputCurrency<Entity> name="default_max_wire_fee" label={i18n`Default max wire fee`} />
-
- <Input<Entity> name="default_wire_fee_amortization" inputType="number" label={i18n`Default wire fee amortization`} />
-
- <InputGroup name="address" label={i18n`Address`}>
- <InputLocation name="address" />
- </InputGroup>
-
- <InputGroup name="jurisdiction" label={i18n`Jurisdiction`}>
- <InputLocation name="jurisdiction" />
- </InputGroup>
-
- <InputDuration<Entity> name="default_pay_delay" label={i18n`Default payment delay`} />
-
- <InputDuration<Entity> name="default_wire_transfer_delay" label={i18n`Default wire transfer delay`} />
+ <DefaultInstanceFormFields showId={false} />
</FormProvider>
<div class="buttons is-right mt-4">
- <button class="button" onClick={onBack} ><Translate>Cancel</Translate></button>
+ <button class="button" onClick={onBack} data-tooltip="cancel operation"><Translate>Cancel</Translate></button>
+
<AsyncButton onClick={submit} disabled={hasErrors} ><Translate>Confirm</Translate></AsyncButton>
</div>
</div>
<div class="column" />
</div>
+
</section>
</div >
diff --git a/packages/frontend/src/scss/main.scss b/packages/frontend/src/scss/main.scss
index 08e2f79..100cadc 100644
--- a/packages/frontend/src/scss/main.scss
+++ b/packages/frontend/src/scss/main.scss
@@ -13,14 +13,14 @@
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)
*/
-
- /* Theme style (colors & sizes) */
- @import "theme-default";
+
+/* Theme style (colors & sizes) */
+@import "theme-default";
/* Core Libs & Lib configs */
@import "libs/all";
@@ -83,28 +83,28 @@ $tooltip-color: red;
}
.toast > .message {
- white-space:pre-wrap;
- opacity:80%;
+ white-space: pre-wrap;
+ opacity: 80%;
}
div {
&.is-loading {
- position: relative;
- pointer-events: none;
- opacity: 0.5;
- &:after {
- // @include loader;
- position: absolute;
- top: calc(50% - 2.5em);
- left: calc(50% - 2.5em);
- width: 5em;
- height: 5em;
- border-width: 0.25em;
- }
+ position: relative;
+ pointer-events: none;
+ opacity: 0.5;
+ &:after {
+ // @include loader;
+ position: absolute;
+ top: calc(50% - 2.5em);
+ left: calc(50% - 2.5em);
+ width: 5em;
+ height: 5em;
+ border-width: 0.25em;
+ }
}
}
-input[type=checkbox]:indeterminate + .check {
+input[type="checkbox"]:indeterminate + .check {
background: red !important;
}
@@ -115,7 +115,7 @@ input[type=checkbox]:indeterminate + .check {
}
.right-sticky .buttons {
- flex-wrap: nowrap
+ flex-wrap: nowrap;
}
.table.is-striped tbody tr:not(.is-selected):nth-child(even) .right-sticky {
@@ -132,13 +132,12 @@ tr:hover .right-sticky {
.content-full-size {
height: calc(100% - 3rem);
position: absolute;
- width: calc(100% - 14rem);
- display:flex;
+ width: calc(100% - 14rem);
+ display: flex;
}
.content-full-size .column .card {
min-width: 200px;
-
}
@include touch {
@@ -146,7 +145,7 @@ tr:hover .right-sticky {
height: 100%;
position: absolute;
width: 100%;
- }
+ }
}
.column.is-half {
@@ -158,9 +157,21 @@ input:read-only {
cursor: initial;
}
-span.icon[data-tooltip]:before {
- z-index: 1000;
- max-width: 250px;
- transform: inherit;
- // white-space: normal;
+[data-tooltip]:before {
+ max-width: 15rem;
+ width: max-content;
+ text-align: left;
+ transition: opacity 0.1s linear 1s;
+ // transform: inherit !important;
+ white-space: pre-wrap !important;
+ font-weight: normal;
+ // position: relative;
+}
+
+.icon[data-tooltip]:before {
+ transition: none;
+}
+
+span[data-tooltip] {
+ border-bottom: none;
}