summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2021-06-22 11:20:49 -0300
committerSebastian <sebasjm@gmail.com>2021-06-24 09:29:41 -0300
commita75d12bb47ceaf17648a8fcc7c9d0e515ae2fcae (patch)
treed783e019c407f728d09abddd7b656520a897da74 /packages
parentba74497459c00a664eef9ccb390e1b917aae08ac (diff)
downloadmerchant-backoffice-a75d12bb47ceaf17648a8fcc7c9d0e515ae2fcae.tar.gz
merchant-backoffice-a75d12bb47ceaf17648a8fcc7c9d0e515ae2fcae.tar.bz2
merchant-backoffice-a75d12bb47ceaf17648a8fcc7c9d0e515ae2fcae.zip
from star to alert, using amounts api
Diffstat (limited to 'packages')
-rw-r--r--packages/frontend/src/components/form/FormProvider.tsx2
-rw-r--r--packages/frontend/src/components/form/Input.tsx4
-rw-r--r--packages/frontend/src/components/form/InputDate.tsx7
-rw-r--r--packages/frontend/src/components/form/InputGroup.tsx5
-rw-r--r--packages/frontend/src/components/form/InputWithAddon.tsx5
-rw-r--r--packages/frontend/src/components/form/useField.tsx16
-rw-r--r--packages/frontend/src/components/form/useGroupField.tsx7
-rw-r--r--packages/frontend/src/components/product/ProductForm.tsx1
-rw-r--r--packages/frontend/src/declaration.d.ts3
-rw-r--r--packages/frontend/src/paths/admin/create/CreatePage.tsx2
-rw-r--r--packages/frontend/src/paths/instance/orders/create/Create.stories.tsx2
-rw-r--r--packages/frontend/src/paths/instance/orders/create/CreatePage.tsx130
-rw-r--r--packages/frontend/src/paths/instance/orders/list/Table.tsx21
-rw-r--r--packages/frontend/src/paths/instance/products/create/CreatePage.tsx2
-rw-r--r--packages/frontend/src/paths/instance/products/update/UpdatePage.tsx2
-rw-r--r--packages/frontend/src/paths/instance/reserves/create/CreatePage.tsx4
-rw-r--r--packages/frontend/src/paths/instance/transfers/create/CreatePage.tsx2
-rw-r--r--packages/frontend/src/paths/instance/update/UpdatePage.tsx9
-rw-r--r--packages/frontend/src/utils/amount.ts15
-rw-r--r--packages/frontend/src/utils/constants.ts2
20 files changed, 137 insertions, 104 deletions
diff --git a/packages/frontend/src/components/form/FormProvider.tsx b/packages/frontend/src/components/form/FormProvider.tsx
index d1fb77b..4855553 100644
--- a/packages/frontend/src/components/form/FormProvider.tsx
+++ b/packages/frontend/src/components/form/FormProvider.tsx
@@ -65,7 +65,7 @@ export function useFormContext<T>() {
}
export type FormErrors<T> = {
- [P in keyof T]?: string
+ [P in keyof T]?: string | FormErrors<T[P]>
}
export type FormtoStr<T> = {
diff --git a/packages/frontend/src/components/form/Input.tsx b/packages/frontend/src/components/form/Input.tsx
index 4147f2c..f7e0b5c 100644
--- a/packages/frontend/src/components/form/Input.tsx
+++ b/packages/frontend/src/components/form/Input.tsx
@@ -59,8 +59,8 @@ 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" />
+ { required && <span class="icon has-text-danger is-right">
+ <i class="mdi mdi-alert" />
</span> }
</p>
{error && <p class="help is-danger">{error}</p>}
diff --git a/packages/frontend/src/components/form/InputDate.tsx b/packages/frontend/src/components/form/InputDate.tsx
index 654d608..614e44a 100644
--- a/packages/frontend/src/components/form/InputDate.tsx
+++ b/packages/frontend/src/components/form/InputDate.tsx
@@ -36,7 +36,7 @@ export function InputDate<T>({ name, readonly, label, placeholder, help, tooltip
const [opened, setOpened] = useState(false)
const i18n = useTranslator()
- const { error, value, onChange } = useField<T>(name);
+ const { error, required, value, onChange } = useField<T>(name);
let strValue = ''
if (!value) {
@@ -61,12 +61,15 @@ export function InputDate<T>({ name, readonly, label, placeholder, help, tooltip
<div class="field-body is-flex-grow-3">
<div class="field">
<div class="field has-addons">
- <p class={expand ? "control is-expanded" : "control"}>
+ <p class={expand ? "control is-expanded has-icons-right" : "control has-icons-right"}>
<input class="input" type="text"
readonly value={strValue}
placeholder={placeholder}
onClick={() => { if (!readonly) setOpened(true) }}
/>
+ { required && <span class="icon has-text-danger is-right">
+ <i class="mdi mdi-alert" />
+ </span> }
{help}
</p>
<div class="control" onClick={() => { if (!readonly) setOpened(true) }}>
diff --git a/packages/frontend/src/components/form/InputGroup.tsx b/packages/frontend/src/components/form/InputGroup.tsx
index 0720cfb..8d1f512 100644
--- a/packages/frontend/src/components/form/InputGroup.tsx
+++ b/packages/frontend/src/components/form/InputGroup.tsx
@@ -36,11 +36,14 @@ export function InputGroup<T>({ name, label, children, tooltip, alternative }: P
return <div class="card">
<header class="card-header">
- <p class={!group?.hasError ? "card-header-title" : "card-header-title has-text-danger"}>
+ <p class="card-header-title">
{label}
{tooltip && <span class="icon" data-tooltip={tooltip}>
<i class="mdi mdi-information" />
</span>}
+ {group?.hasError && <span class="icon has-text-danger" data-tooltip={tooltip}>
+ <i class="mdi mdi-alert" />
+ </span>}
</p>
<button class="card-header-icon" aria-label="more options" onClick={(): void => setActive(!active)}>
<span class="icon">
diff --git a/packages/frontend/src/components/form/InputWithAddon.tsx b/packages/frontend/src/components/form/InputWithAddon.tsx
index cb84c86..d2905df 100644
--- a/packages/frontend/src/components/form/InputWithAddon.tsx
+++ b/packages/frontend/src/components/form/InputWithAddon.tsx
@@ -19,7 +19,6 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { ComponentChildren, h, VNode } from "preact";
-import { useTranslator } from "../../i18n";
import { InputProps, useField } from "./useField";
export interface Props<T> extends InputProps<T> {
@@ -60,8 +59,8 @@ export function InputWithAddon<T>({ name, readonly, addonBefore, children, expan
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" />
+ {required && <span class="icon has-text-danger is-right">
+ <i class="mdi mdi-alert" />
</span>}
{help}
{children}
diff --git a/packages/frontend/src/components/form/useField.tsx b/packages/frontend/src/components/form/useField.tsx
index c552711..a04be70 100644
--- a/packages/frontend/src/components/form/useField.tsx
+++ b/packages/frontend/src/components/form/useField.tsx
@@ -29,7 +29,7 @@ interface Use<V> {
initial: any;
onChange: (v: V) => void;
toStr: (f: V | undefined) => string;
- fromStr: (v: string) => V
+ fromStr: (v: string) => V
}
export function useField<T>(name: keyof T): Use<T[typeof name]> {
@@ -42,16 +42,20 @@ export function useField<T>(name: keyof T): Use<T[typeof name]> {
return setValueDeeper(prev, String(field).split('.'), value)
})
}
-
- const defaultToString = ((f?: V):string => String(!f ? '': f))
- const defaultFromString = ((v: string):V => v as any)
+
+ const defaultToString = ((f?: V): string => String(!f ? '' : f))
+ const defaultFromString = ((v: string): V => v as any)
const value = readField(object, String(name))
const initial = readField(initialObject, String(name))
const isDirty = value !== initial
const hasError = readField(errors, String(name))
+ if (name == 'pricing.order_price') {
+
+ console.log(value, initial, value === initial)
+ }
return {
error: isDirty ? hasError : undefined,
- required: !isDirty && hasError,
+ required: !isDirty && hasError,
value,
initial,
onChange: updateField(name) as any,
@@ -73,7 +77,7 @@ const readField = (object: any, name: string) => {
const setValueDeeper = (object: any, names: string[], value: any): any => {
if (names.length === 0) return value
const [head, ...rest] = names
- return {...object, [head]: setValueDeeper(object[head] || {}, rest, value) }
+ return { ...object, [head]: setValueDeeper(object[head] || {}, rest, value) }
}
export interface InputProps<T> {
diff --git a/packages/frontend/src/components/form/useGroupField.tsx b/packages/frontend/src/components/form/useGroupField.tsx
index 0a809e4..a73f464 100644
--- a/packages/frontend/src/components/form/useGroupField.tsx
+++ b/packages/frontend/src/components/form/useGroupField.tsx
@@ -30,8 +30,11 @@ export function useGroupField<T>(name: keyof T): Use {
if (!f)
return {};
- const RE = new RegExp(`^${name}`);
return {
- hasError: Object.keys(f.errors).some(e => RE.test(e))
+ hasError: readField(f.errors, String(name))
};
}
+
+const readField = (object: any, name: string) => {
+ return name.split('.').reduce((prev, current) => prev && prev[current], object)
+}
diff --git a/packages/frontend/src/components/product/ProductForm.tsx b/packages/frontend/src/components/product/ProductForm.tsx
index 09b5744..9e8ac97 100644
--- a/packages/frontend/src/components/product/ProductForm.tsx
+++ b/packages/frontend/src/components/product/ProductForm.tsx
@@ -51,6 +51,7 @@ export function ProductForm({ onSubscribe, initial, alreadyExist, }: Props) {
taxes: [],
next_restock: { t_ms: 'never' },
...initial,
+ price: ':0',
stock: !initial || initial.total_stock === -1 ? undefined : {
current: initial.total_stock || 0,
lost: initial.total_lost || 0,
diff --git a/packages/frontend/src/declaration.d.ts b/packages/frontend/src/declaration.d.ts
index 82bc694..1722a3d 100644
--- a/packages/frontend/src/declaration.d.ts
+++ b/packages/frontend/src/declaration.d.ts
@@ -844,9 +844,6 @@ export namespace MerchantBackend {
fulfillment_url?: string;
}
- // FIXME: Where is this being used?
- // type ProductSpecification = (MinimalInventoryProduct | Product);
-
interface MinimalInventoryProduct {
// Which product is requested (here mandatory!)
product_id: string;
diff --git a/packages/frontend/src/paths/admin/create/CreatePage.tsx b/packages/frontend/src/paths/admin/create/CreatePage.tsx
index 196284e..c9276d7 100644
--- a/packages/frontend/src/paths/admin/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/admin/create/CreatePage.tsx
@@ -130,7 +130,7 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode {
<div class="buttons is-right mt-5">
{onBack && <button class="button" onClick={onBack}><Translate>Cancel</Translate></button>}
<AsyncButton onClick={submit} disabled={!isTokenSet || hasErrors} data-tooltip={
- hasErrors ? i18n`Need to complete fields marked with a star and choose authorization method` : 'confirm operation'
+ hasErrors ? i18n`Need to complete marked fields and choose authorization method` : 'confirm operation'
}><Translate>Confirm</Translate></AsyncButton>
</div>
diff --git a/packages/frontend/src/paths/instance/orders/create/Create.stories.tsx b/packages/frontend/src/paths/instance/orders/create/Create.stories.tsx
index daf48c0..a1d37e2 100644
--- a/packages/frontend/src/paths/instance/orders/create/Create.stories.tsx
+++ b/packages/frontend/src/paths/instance/orders/create/Create.stories.tsx
@@ -43,7 +43,7 @@ export const Example = createExample(TestedComponent, {
default_max_deposit_fee: '',
default_max_wire_fee: '',
default_pay_delay: {
- d_ms: 'forever'
+ d_ms: new Date().getTime()
},
default_wire_fee_amortization: 1
},
diff --git a/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx b/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
index ecc2f0a..e4d19a8 100644
--- a/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
@@ -19,10 +19,10 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { add } from "date-fns";
+import { add, isBefore, isFuture } from "date-fns";
+import { AmountJson, Amounts } from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";
-import * as yup from 'yup';
import { FormProvider, FormErrors } from "../../../../components/form/FormProvider";
import { Input } from "../../../../components/form/Input";
import { InputCurrency } from "../../../../components/form/InputCurrency";
@@ -34,7 +34,7 @@ import { useConfigContext } from "../../../../context/config";
import { Duration, MerchantBackend, WithId } from "../../../../declaration";
import { Translate, useTranslator } from "../../../../i18n";
import { OrderCreateSchema as schema } from '../../../../schemas/index';
-import { multiplyPrice, rate, sumPrices } from "../../../../utils/amount";
+import { rate } from "../../../../utils/amount";
import { InventoryProductForm } from "./InventoryProductForm";
import { NonInventoryProductFrom } from "./NonInventoryProductForm";
@@ -59,14 +59,15 @@ function with_defaults(config: InstanceConfig): Partial<Entity> {
return {
inventoryProducts: {},
products: [],
- pricing: {} as any,
+ pricing: {
+ },
payments: {
max_wire_fee: config.default_max_wire_fee,
max_fee: config.default_max_deposit_fee,
wire_fee_amortization: config.default_wire_fee_amortization,
pay_deadline: defaultPayDeadline,
refund_deadline: defaultPayDeadline,
- } as Partial<Payments>,
+ },
extra: ''
};
}
@@ -98,24 +99,60 @@ interface Payments {
interface Entity {
inventoryProducts: ProductMap,
products: MerchantBackend.Product[],
- pricing: Pricing;
- payments: Payments;
+ pricing: Partial<Pricing>;
+ payments: Partial<Payments>;
extra: string;
}
+const stringIsValidJSON = (value: string) => {
+ try {
+ JSON.parse(value.trim())
+ return true
+ } catch {
+ return false
+ }
+}
+
+
export function CreatePage({ onCreate, onBack, instanceConfig, instanceInventory }: Props): VNode {
+ const config = useConfigContext()
+ const zero = Amounts.getZero(config.currency)
const [value, valueHandler] = useState(with_defaults(instanceConfig))
- // const [errors, setErrors] = useState<FormErrors<Entity>>({})
const inventoryList = Object.values(value.inventoryProducts || {})
const productList = Object.values(value.products || {})
- let errors: FormErrors<Entity> = {}
- try {
- schema.validateSync(value, { abortEarly: false })
- } catch (err) {
- const yupErrors = err.inner as yup.ValidationError[]
- errors = yupErrors.reduce((prev, cur) => !cur.path ? prev : ({ ...prev, [cur.path]: cur.message }), {})
+ const i18n = useTranslator()
+
+ function check<T>(obj: T): T | undefined {
+ return Object.keys(obj).some(k => (obj as any)[k] !== undefined) ? obj : undefined
+ }
+
+ const errors: FormErrors<Entity> = {
+ pricing: {
+ summary: !value.pricing?.summary ? i18n`required`:undefined,
+ order_price: !value.pricing?.order_price ? i18n`required`: (
+ (Amounts.parse(value.pricing.order_price)?.value || 0) <= 0 ? i18n`must be greater than 0` : undefined
+ )
+ },
+ extra: value.extra && !stringIsValidJSON(value.extra) ? i18n`not a valid json` : undefined,
+ payments: check({
+ refund_deadline: !value.payments?.refund_deadline ? i18n`required` : (
+ !isFuture(value.payments.refund_deadline) ? i18n`should be in the future` : (
+ value.payments.pay_deadline && value.payments.refund_deadline && isBefore(value.payments.refund_deadline, value.payments.pay_deadline) ?
+ i18n`pay deadline cannot be before refund deadline` : undefined
+ )
+ ),
+ pay_deadline: !value.payments?.pay_deadline ? i18n`required` : (
+ !isFuture(value.payments.pay_deadline) ? i18n`should be in the future` : undefined
+ ),
+ auto_refund_deadline: !value.payments?.auto_refund_deadline ? undefined : (
+ !isFuture(value.payments.auto_refund_deadline) ? i18n`should be in the future` : undefined
+ ),
+ delivery_date: !value.payments?.delivery_date ? undefined : (
+ !isFuture(value.payments.delivery_date) ? i18n`should be in the future` : undefined
+ ),
+ }),
}
const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== undefined)
@@ -147,8 +184,6 @@ export function CreatePage({ onCreate, onBack, instanceConfig, instanceInventory
onCreate(request);
}
- const config = useConfigContext()
-
const addProductToTheInventoryList = (product: MerchantBackend.Products.ProductDetail & WithId, quantity: number) => {
valueHandler(v => {
const inventoryProducts = { ...v.inventoryProducts }
@@ -182,38 +217,44 @@ export function CreatePage({ onCreate, onBack, instanceConfig, instanceInventory
const [editingProduct, setEditingProduct] = useState<MerchantBackend.Product | undefined>(undefined)
- const totalPriceInventory = inventoryList.reduce((prev, cur) => sumPrices(prev, multiplyPrice(cur.product.price, cur.quantity)), `${config.currency}:0`)
- const totalPriceProducts = productList.reduce((prev, cur) => sumPrices(prev, multiplyPrice(cur.price, cur.quantity)), `${config.currency}:0`)
+ const totalPriceInventory = inventoryList.reduce((prev, cur) => {
+ const p = Amounts.parseOrThrow(cur.product.price)
+ return Amounts.add(prev, Amounts.mult(p, cur.quantity).amount).amount
+ }, zero)
+
+ const totalPriceProducts = productList.reduce((prev, cur) => {
+ const p = Amounts.parseOrThrow(cur.price)
+ return Amounts.add(prev, Amounts.mult(p, cur.quantity).amount).amount
+ }, zero)
const hasProducts = inventoryList.length > 0 || productList.length > 0
- const totalPrice = sumPrices(totalPriceInventory, totalPriceProducts)
+ const totalPrice = Amounts.add(totalPriceInventory, totalPriceProducts)
useEffect(() => {
valueHandler(v => {
return ({
...v, pricing: {
...v.pricing!,
- products_price: totalPrice,
- order_price: totalPrice,
+ // products_price: (Amounts.isZero(totalPrice.amount) ? undefined : Amounts.stringify(totalPrice.amount))!,
+ // order_price: (Amounts.isZero(totalPrice.amount) ? undefined : Amounts.stringify(totalPrice.amount))!,
+ // products_price: Amounts.stringify(totalPrice.amount),
+ // order_price: Amounts.stringify(totalPrice.amount),
}
})
})
}, [hasProducts, totalPrice])
+ const discountOrRise = rate(value.pricing?.order_price || `${config.currency}:0`, Amounts.stringify(totalPrice.amount))
- const discountOrRise = rate(value.pricing?.order_price || `${config.currency}:0`, totalPrice)
-
- useEffect(() => {
- valueHandler(v => {
- return ({
- ...v, pricing: {
- ...v.pricing!
- }
- })
- })
- }, [value.pricing?.order_price])
-
- const i18n = useTranslator()
+ // useEffect(() => {
+ // valueHandler(v => {
+ // return ({
+ // ...v, pricing: {
+ // ...v.pricing!
+ // }
+ // })
+ // })
+ // }, [value.pricing?.order_price])
return <div>
@@ -223,11 +264,10 @@ export function CreatePage({ onCreate, onBack, instanceConfig, instanceInventory
<div class="column is-four-fifths">
<InputGroup name="inventory_products" label={i18n`Manage products from inventory in order`} alternative={
- inventoryList.length > 0 &&
- // FIXME: translating plural singular
- <p>
+ inventoryList.length > 0 && <p>
+ {/* // FIXME: translating plural singular */}
{inventoryList.length} products
- with a total price of {totalPriceInventory}.
+ with a total price of {Amounts.stringify(totalPriceInventory)}.
</p>
} tooltip={i18n`Manage list of products from managed inventory included in the order.`}>
<InventoryProductForm
@@ -249,9 +289,9 @@ export function CreatePage({ onCreate, onBack, instanceConfig, instanceInventory
<InputGroup name="products" label={i18n`Manage products outside of inventory in order`} alternative={
productList.length > 0 && <p>
- // FIXME: translating plural singular
+ {/* // FIXME: translating plural singular */}
{productList.length} products
- with a total price of {totalPriceProducts}.
+ with a total price of {Amounts.stringify(totalPriceProducts)}.
</p>
} tooltip={i18n`Manage list of products without inventory management included in the order.`}>
<NonInventoryProductFrom productToEdit={editingProduct} onAddProduct={(p) => {
@@ -280,7 +320,7 @@ export function CreatePage({ onCreate, onBack, instanceConfig, instanceInventory
<InputCurrency name="pricing.products_price" label={i18n`Total price`} readonly tooltip={i18n`total product price added up`} />
<InputCurrency name="pricing.order_price"
label={i18n`Total price`}
- addonAfter={value.pricing?.order_price !== totalPrice && (discountOrRise < 1 ?
+ addonAfter={discountOrRise > 0 && (discountOrRise < 1 ?
`discount of %${Math.round((1 - discountOrRise) * 100)}` :
`rise of %${Math.round((discountOrRise - 1) * 100)}`)
}
@@ -302,14 +342,14 @@ export function CreatePage({ onCreate, onBack, instanceConfig, instanceInventory
<InputLocation name="payments.delivery_location" />
</InputGroup>}
- <InputCurrency name="payments.max_fee" label={i18n`Maximum deposit fee`} tooltip={i18n`Maximum deposit fees the merchant is willing to cover for this order. Higher deposit fees must be covered in full by the consumer.`} />
- <InputCurrency name="payments.max_wire_fee" label={i18n`Maximum wire fee`} tooltip={i18n`Maximum aggregate wire fees the merchant is willing to cover for this order. Wire fees exceeding this amount are to be covered by the customers.`}/>
- <Input name="payments.wire_fee_amortization" label={i18n`Wire fee amortization`} tooltip={i18n`Factor by which wire fees exceeding the above threshold are divided to determine the share of excess wire fees to be paid explicitly by the consumer.`}/>
+ <InputCurrency name="payments.max_fee" label={i18n`Maximum deposit fee`} tooltip={i18n`Maximum deposit fees the merchant is willing to cover for this order. Higher deposit fees must be covered in full by the consumer.`} />
+ <InputCurrency name="payments.max_wire_fee" label={i18n`Maximum wire fee`} tooltip={i18n`Maximum aggregate wire fees the merchant is willing to cover for this order. Wire fees exceeding this amount are to be covered by the customers.`} />
+ <Input name="payments.wire_fee_amortization" label={i18n`Wire fee amortization`} tooltip={i18n`Factor by which wire fees exceeding the above threshold are divided to determine the share of excess wire fees to be paid explicitly by the consumer.`} />
<Input name="payments.fullfilment_url" label={i18n`Fulfillment URL`} tooltip={i18n`URL to which the user will be redirected after successful payment.`} />
</InputGroup>
<InputGroup name="extra" label={i18n`Additional information`} tooltip={i18n`Custom information to be included in the contract for this order.`}>
- <Input name="extra" inputType="multiline" label={`Value`} tooltip={i18n`You must enter a value in JavaScript Object Notation (JSON).`} />
+ <Input name="extra" inputType="multiline" label={`Value`} tooltip={i18n`You must enter a value in JavaScript Object Notation (JSON).`} />
</InputGroup>
</FormProvider>
diff --git a/packages/frontend/src/paths/instance/orders/list/Table.tsx b/packages/frontend/src/paths/instance/orders/list/Table.tsx
index ed8ee3a..64a5d22 100644
--- a/packages/frontend/src/paths/instance/orders/list/Table.tsx
+++ b/packages/frontend/src/paths/instance/orders/list/Table.tsx
@@ -30,9 +30,11 @@ import { InputSelector } from "../../../../components/form/InputSelector";
import { ConfirmModal } from "../../../../components/modal";
import { MerchantBackend, WithId } from "../../../../declaration";
import { Translate, useTranslator } from "../../../../i18n";
-import { RefundSchema as RefundSchema } from "../../../../schemas";
-import { mergeRefunds, subtractPrices, sumPrices } from "../../../../utils/amount";
+import { RefundSchema } from "../../../../schemas";
+import { mergeRefunds } from "../../../../utils/amount";
import { AMOUNT_ZERO_REGEX } from "../../../../utils/constants";
+import { Amounts } from "@gnu-taler/taler-util";
+import { useConfigContext } from "../../../../context/config";
type Entity = MerchantBackend.Orders.OrderHistoryEntry & WithId
interface Props {
@@ -174,13 +176,14 @@ export function RefundModal({ order, onCancel, onConfirm }: RefundModalProps): V
}
}
- const refunds = (order.order_status === 'paid' ? order.refund_details : [])
- .reduce(mergeRefunds, [])
- const totalRefunded = refunds.map(r => r.amount).reduce((p, c) => sumPrices(c, p), ':0')
- const orderPrice = (order.order_status === 'paid' ? order.contract_terms.amount : undefined)
- const totalRefundable = !orderPrice ? undefined : (refunds.length ? subtractPrices(orderPrice, totalRefunded) : orderPrice)
+ const refunds = (order.order_status === 'paid' ? order.refund_details : []).reduce(mergeRefunds, [])
- const isRefundable = totalRefundable && !AMOUNT_ZERO_REGEX.test(totalRefundable)
+ const config = useConfigContext()
+ const totalRefunded = refunds.map(r => r.amount).reduce((p, c) => Amounts.add(p, Amounts.parseOrThrow(c)).amount, Amounts.getZero(config.currency) )
+ const orderPrice = (order.order_status === 'paid' ? Amounts.parseOrThrow(order.contract_terms.amount) : undefined)
+ const totalRefundable = !orderPrice ? Amounts.getZero(totalRefunded.currency) : (refunds.length ? Amounts.sub(orderPrice, totalRefunded).amount : orderPrice)
+
+ const isRefundable = Amounts.isNonZero(totalRefundable)
//FIXME: parameters in the translation
return <ConfirmModal description="refund" danger active onCancel={onCancel} onConfirm={validateAndConfirm}>
{refunds.length > 0 && <div class="columns">
@@ -212,7 +215,7 @@ export function RefundModal({ order, onCancel, onConfirm }: RefundModalProps): V
{isRefundable && <FormProvider<State> errors={errors} object={form} valueHandler={(d) => setValue(d as any)}>
<InputCurrency<State> name="refund" label={i18n`Refund`} tooltip={i18n`amount to be refunded`}>
- <Translate>Max refundable:</Translate> {totalRefundable}
+ <Translate>Max refundable:</Translate> {Amounts.stringify(totalRefundable)}
</InputCurrency>
<InputSelector name="mainReason" label={i18n`Reason`} values={[i18n`duplicated`, i18n`requested by the customer`, i18n`other`]} tooltip={i18n`why this order is being refunded`} />
{form.mainReason && <Input<State> label={i18n`Description`} name="description" tooltip={i18n`more information to give context`} />}
diff --git a/packages/frontend/src/paths/instance/products/create/CreatePage.tsx b/packages/frontend/src/paths/instance/products/create/CreatePage.tsx
index 0f2411b..ed669f6 100644
--- a/packages/frontend/src/paths/instance/products/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/instance/products/create/CreatePage.tsx
@@ -53,7 +53,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
<div class="buttons is-right mt-5">
{onBack && <button class="button" onClick={onBack} ><Translate>Cancel</Translate></button>}
<AsyncButton onClick={submitForm} data-tooltip={
- !submitForm ? i18n`Need to complete fields marked with a star` : 'confirm operation'
+ !submitForm ? i18n`Need to complete marked fields` : 'confirm operation'
} disabled={!submitForm}><Translate>Confirm</Translate></AsyncButton>
</div>
diff --git a/packages/frontend/src/paths/instance/products/update/UpdatePage.tsx b/packages/frontend/src/paths/instance/products/update/UpdatePage.tsx
index 32d67c0..d7eb3d1 100644
--- a/packages/frontend/src/paths/instance/products/update/UpdatePage.tsx
+++ b/packages/frontend/src/paths/instance/products/update/UpdatePage.tsx
@@ -66,7 +66,7 @@ export function UpdatePage({ product, onUpdate, onBack }: Props): VNode {
<div class="buttons is-right mt-5">
{onBack && <button class="button" onClick={onBack} ><Translate>Cancel</Translate></button>}
<AsyncButton onClick={submitForm} data-tooltip={
- !submitForm ? i18n`Need to complete fields marked with a star` : 'confirm operation'
+ !submitForm ? i18n`Need to complete marked fields` : 'confirm operation'
} disabled={!submitForm}><Translate>Confirm</Translate></AsyncButton>
</div>
</div>
diff --git a/packages/frontend/src/paths/instance/reserves/create/CreatePage.tsx b/packages/frontend/src/paths/instance/reserves/create/CreatePage.tsx
index 05f5e06..2e85cf9 100644
--- a/packages/frontend/src/paths/instance/reserves/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/instance/reserves/create/CreatePage.tsx
@@ -92,7 +92,7 @@ function ViewStep({ step, setCurrentStep, reserve, onBack, submitForm, setReserv
setExchangeQueryError(r.message)
})
}} data-tooltip={
- hasErrors ? i18n`Need to complete fields marked with a star` : 'confirm operation'
+ hasErrors ? i18n`Need to complete marked fields` : 'confirm operation'
} disabled={hasErrors} ><Translate>Next</Translate></AsyncButton>
</div>
</Fragment>
@@ -113,7 +113,7 @@ function ViewStep({ step, setCurrentStep, reserve, onBack, submitForm, setReserv
<div class="buttons is-right mt-5">
{onBack && <button class="button" onClick={() => setCurrentStep(Steps.EXCHANGE)} ><Translate>Back</Translate></button>}
<AsyncButton onClick={submitForm} data-tooltip={
- hasErrors ? i18n`Need to complete fields marked with a star` : 'confirm operation'
+ hasErrors ? i18n`Need to complete marked fields` : 'confirm operation'
} disabled={hasErrors} ><Translate>Confirm</Translate></AsyncButton>
</div>
</Fragment>
diff --git a/packages/frontend/src/paths/instance/transfers/create/CreatePage.tsx b/packages/frontend/src/paths/instance/transfers/create/CreatePage.tsx
index 5c74326..566483e 100644
--- a/packages/frontend/src/paths/instance/transfers/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/instance/transfers/create/CreatePage.tsx
@@ -94,7 +94,7 @@ export function CreatePage({ accounts, onCreate, onBack }: Props): VNode {
<div class="buttons is-right mt-5">
{onBack && <button class="button" onClick={onBack} ><Translate>Cancel</Translate></button>}
<AsyncButton disabled={hasErrors} data-tooltip={
- hasErrors ? i18n`Need to complete fields marked with a star` : 'confirm operation'
+ hasErrors ? i18n`Need to complete marked fields` : 'confirm operation'
} onClick={submitForm} ><Translate>Confirm</Translate></AsyncButton>
</div>
diff --git a/packages/frontend/src/paths/instance/update/UpdatePage.tsx b/packages/frontend/src/paths/instance/update/UpdatePage.tsx
index 7612d6f..c900192 100644
--- a/packages/frontend/src/paths/instance/update/UpdatePage.tsx
+++ b/packages/frontend/src/paths/instance/update/UpdatePage.tsx
@@ -24,13 +24,6 @@ import { useState } from "preact/hooks";
import * as yup from 'yup';
import { AsyncButton } from "../../../components/exception/AsyncButton";
import { FormProvider, FormErrors } 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 { UpdateTokenModal } from "../../../components/modal";
import { useInstanceContext } from "../../../context/instance";
import { MerchantBackend } from "../../../declaration";
@@ -157,7 +150,7 @@ export function UpdatePage({ onUpdate, onChangeAuth, selected, onBack }: Props):
<button class="button" onClick={onBack} data-tooltip="cancel operation"><Translate>Cancel</Translate></button>
<AsyncButton onClick={submit} data-tooltip={
- hasErrors ? i18n`Need to complete fields marked with a star` : 'confirm operation'
+ hasErrors ? i18n`Need to complete marked fields` : 'confirm operation'
} disabled={hasErrors} ><Translate>Confirm</Translate></AsyncButton>
</div>
</div>
diff --git a/packages/frontend/src/utils/amount.ts b/packages/frontend/src/utils/amount.ts
index 823ad9d..062ddaf 100644
--- a/packages/frontend/src/utils/amount.ts
+++ b/packages/frontend/src/utils/amount.ts
@@ -22,7 +22,7 @@ import { MerchantBackend } from "../declaration";
* @param two
* @returns
*/
-export const sumPrices = (one: string, two: string) => {
+const sumPrices = (one: string, two: string) => {
const [currency, valueOne] = one.split(':')
const [, valueTwo] = two.split(':')
return `${currency}:${parseInt(valueOne, 10) + parseInt(valueTwo, 10)}`
@@ -55,19 +55,6 @@ export function mergeRefunds(prev: MerchantBackend.Orders.RefundDetails[], cur:
return prev
}
-export const multiplyPrice = (price: string, q: number) => {
- const a = Amounts.parseOrThrow(price)
- const r = Amounts.mult(a, q)
- return Amounts.stringify(r.amount)
-}
-
-export const subtractPrices = (one: string, two: string) => {
- const a = Amounts.parseOrThrow(one)
- const b = Amounts.parseOrThrow(two)
- const r = Amounts.sub(a, b)
- return Amounts.stringify(r.amount)
-}
-
export const rate = (one: string, two: string) => {
const a = Amounts.parseOrThrow(one)
const b = Amounts.parseOrThrow(two)
diff --git a/packages/frontend/src/utils/constants.ts b/packages/frontend/src/utils/constants.ts
index 403adb9..cbf4342 100644
--- a/packages/frontend/src/utils/constants.ts
+++ b/packages/frontend/src/utils/constants.ts
@@ -23,7 +23,7 @@
export const PAYTO_REGEX = /^payto:\/\/[a-zA-Z][a-zA-Z0-9-.]+(\/[a-zA-Z0-9\-\.\~\(\)@_%:!$&'*+,;=]*)*\??((amount|receiver-name|sender-name|instruction|message)=[a-zA-Z0-9\-\.\~\(\)@_%:!$'*+,;=]*&?)*$/
export const PAYTO_WIRE_METHOD_LOOKUP = /payto:\/\/([a-zA-Z][a-zA-Z0-9-.]+)\/.*/
-export const AMOUNT_REGEX = /^[a-zA-Z][a-zA-Z]*:[0-9][0-9,]*\.?[0-9,]*$/
+export const AMOUNT_REGEX = /^[a-zA-Z]*:[0-9][0-9,]*\.?[0-9,]*$/
export const INSTANCE_ID_LOOKUP = /^\/instances\/([^/]*)\/?$/