summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2021-06-07 09:42:16 -0300
committerSebastian <sebasjm@gmail.com>2021-06-07 09:42:32 -0300
commit0eacc9ad11caa53efe5c71add7028251d058b753 (patch)
tree5a22f69dff3f3d6359439c79a95440a7eb8d50b7
parenta3d5b4882aa76bd56b8ac70f5b2a927bd7ba5b5d (diff)
downloadmerchant-backoffice-0eacc9ad11caa53efe5c71add7028251d058b753.tar.gz
merchant-backoffice-0eacc9ad11caa53efe5c71add7028251d058b753.tar.bz2
merchant-backoffice-0eacc9ad11caa53efe5c71add7028251d058b753.zip
add more information about reserve
-rw-r--r--packages/frontend/src/declaration.d.ts38
-rw-r--r--packages/frontend/src/paths/instance/orders/create/CreatePage.tsx25
-rw-r--r--packages/frontend/src/paths/instance/reserves/create/CreatedSuccessfully.tsx4
-rw-r--r--packages/frontend/src/paths/instance/reserves/details/DetailPage.tsx63
-rw-r--r--packages/frontend/src/paths/instance/reserves/details/index.tsx4
-rw-r--r--packages/frontend/src/paths/instance/reserves/list/Table.tsx12
-rw-r--r--packages/frontend/src/utils/amount.ts10
7 files changed, 107 insertions, 49 deletions
diff --git a/packages/frontend/src/declaration.d.ts b/packages/frontend/src/declaration.d.ts
index 6717566..21199f4 100644
--- a/packages/frontend/src/declaration.d.ts
+++ b/packages/frontend/src/declaration.d.ts
@@ -56,45 +56,45 @@ export namespace ExchangeBackend {
// Master public key of the exchange, must match the key returned in /keys.
master_public_key: EddsaPublicKey;
-
+
// Array of wire accounts operated by the exchange for
// incoming wire transfers.
accounts: WireAccount[];
-
+
// Object mapping names of wire methods (i.e. "sepa" or "x-taler-bank")
// to wire fees.
- fees: { method : AggregateTransferFee };
- }
- interface WireAccount {
+ fees: { method: AggregateTransferFee };
+ }
+ interface WireAccount {
// payto:// URI identifying the account and wire method
payto_uri: string;
-
+
// Signature using the exchange's offline key
// with purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS.
master_sig: EddsaSignature;
- }
- interface AggregateTransferFee {
+ }
+ interface AggregateTransferFee {
// Per transfer wire transfer fee.
wire_fee: Amount;
-
+
// Per transfer closing fee.
closing_fee: Amount;
-
+
// What date (inclusive) does this fee go into effect?
// The different fees must cover the full time period in which
// any of the denomination keys are valid without overlap.
start_date: Timestamp;
-
+
// What date (exclusive) does this fee stop going into effect?
// The different fees must cover the full time period in which
// any of the denomination keys are valid without overlap.
end_date: Timestamp;
-
+
// Signature of TALER_MasterWireFeePS with
// purpose TALER_SIGNATURE_MASTER_WIRE_FEES.
sig: EddsaSignature;
- }
-
+ }
+
}
export namespace MerchantBackend {
interface ErrorDetail {
@@ -1000,7 +1000,17 @@ export namespace MerchantBackend {
// Is this reserve active (false if it was deleted but not purged)?
active: boolean;
+
+ // URI to use to fill the reserve, can be NULL
+ // if the reserve is inactive or was already filled
+ payto_uri: string;
+
+ // URL of the exchange hosting the reserve,
+ // NULL if the reserve is inactive
+ exchange_url: string;
+
}
+
interface TipStatusEntry {
// Unique identifier for the tip.
diff --git a/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx b/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
index 9d22fc8..ef25500 100644
--- a/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
@@ -44,7 +44,7 @@ interface Props {
onBack?: () => void;
}
-function with_defaults(): Entity {
+function with_defaults(): Partial<Entity> {
return {
inventoryProducts: {},
products: [],
@@ -90,8 +90,8 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
const [value, valueHandler] = useState(with_defaults())
// const [errors, setErrors] = useState<FormErrors<Entity>>({})
- const inventoryList = Object.values(value.inventoryProducts)
- const productList = Object.values(value.products)
+ const inventoryList = Object.values(value.inventoryProducts || {})
+ const productList = Object.values(value.products || {})
let errors: FormErrors<Entity> = {}
try {
@@ -104,6 +104,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
const submit = (): void => {
const order = schema.cast(value)
+ if (!value.payments) return;
const request: MerchantBackend.Orders.PostOrderRequest = {
order: {
@@ -149,14 +150,14 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
const addNewProduct = async (product: MerchantBackend.Product) => {
return valueHandler(v => {
- const products = [...v.products, product]
+ const products = v.products ? [...v.products, product] : []
return ({ ...v, products })
})
}
const removeFromNewProduct = (index: number) => {
valueHandler(v => {
- const products = [...v.products]
+ const products = v.products ? [...v.products] : []
products.splice(index, 1)
return ({ ...v, products })
})
@@ -174,7 +175,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
valueHandler(v => {
return ({
...v, pricing: {
- ...v.pricing,
+ ...v.pricing!,
products_price: totalPrice,
order_price: totalPrice,
}
@@ -183,17 +184,17 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
}, [hasProducts, totalPrice])
- const discountOrRise = rate(value.pricing.order_price, totalPrice)
+ const discountOrRise = rate(value.pricing?.order_price || `${config.currency}:0`, totalPrice)
useEffect(() => {
valueHandler(v => {
return ({
...v, pricing: {
- ...v.pricing,
+ ...v.pricing!
}
})
})
- }, [value.pricing.order_price])
+ }, [value.pricing?.order_price])
const details_response = useInstanceDetails()
@@ -236,7 +237,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
</p>
} tooltip={i18n`add products to the order that already exist in the inventory`}>
<InventoryProductForm
- currentProducts={value.inventoryProducts}
+ currentProducts={value.inventoryProducts || {}}
onAddProduct={addProductToTheInventoryList}
/>
@@ -280,7 +281,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
<InputCurrency name="pricing.products_price" label={i18n`Total price`} readonly tooltip={i18n`total product price added up`} />
<InputCurrency name="pricing.order_price"
label={i18n`Order price`}
- addonAfter={value.pricing.order_price !== totalPrice && (discountOrRise < 1 ?
+ addonAfter={value.pricing?.order_price !== totalPrice && (discountOrRise < 1 ?
`discount of %${Math.round((1 - discountOrRise) * 100)}` :
`rise of %${Math.round((discountOrRise - 1) * 100)}`)
}
@@ -298,7 +299,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
<InputDate name="payments.pay_deadline" label={i18n`Payment deadline`} tooltip={i18n`time until the order can be paid`} />
<InputDate name="payments.delivery_date" label={i18n`Delivery date`} tooltip={i18n`when the order will be delivered, if delivery required`} />
- {value.payments.delivery_date && <InputGroup name="payments.delivery_location" label={i18n`Location`} tooltip={i18n`where the order will be delivered`} >
+ {value.payments?.delivery_date && <InputGroup name="payments.delivery_location" label={i18n`Location`} tooltip={i18n`where the order will be delivered`} >
<InputLocation name="payments.delivery_location" />
</InputGroup>}
diff --git a/packages/frontend/src/paths/instance/reserves/create/CreatedSuccessfully.tsx b/packages/frontend/src/paths/instance/reserves/create/CreatedSuccessfully.tsx
index 2deae14..d173d7b 100644
--- a/packages/frontend/src/paths/instance/reserves/create/CreatedSuccessfully.tsx
+++ b/packages/frontend/src/paths/instance/reserves/create/CreatedSuccessfully.tsx
@@ -18,7 +18,7 @@ import { CreatedSuccessfully as Template } from "../../../../components/notifica
import { MerchantBackend } from "../../../../declaration";
import { Translate } from "../../../../i18n";
-type Entity = {request: MerchantBackend.Tips.ReserveCreateRequest, response: MerchantBackend.Tips.ReserveCreateConfirmation};
+type Entity = { request: MerchantBackend.Tips.ReserveCreateRequest, response: MerchantBackend.Tips.ReserveCreateConfirmation };
interface Props {
entity: Entity;
@@ -81,7 +81,7 @@ export function CreatedSuccessfully({ entity, onConfirm, onCreateAnother }: Prop
<p class="is-size-5"><Translate>For example:</Translate></p>
<pre>
- {entity.response.payto_uri}?message={entity.response.reserve_pub}&amount={entity.request.initial_balance}
+ {entity.response.payto_uri}?message={entity.response.reserve_pub}&amount={entity.request.initial_balance}
</pre>
</Template>;
}
diff --git a/packages/frontend/src/paths/instance/reserves/details/DetailPage.tsx b/packages/frontend/src/paths/instance/reserves/details/DetailPage.tsx
index 08b463a..08942f6 100644
--- a/packages/frontend/src/paths/instance/reserves/details/DetailPage.tsx
+++ b/packages/frontend/src/paths/instance/reserves/details/DetailPage.tsx
@@ -19,6 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
+import { Amounts } from "@gnu-taler/taler-util";
import { format, isAfter } from "date-fns";
import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
@@ -42,22 +43,55 @@ type CT = MerchantBackend.ContractTerms
interface Props {
onBack: () => void;
selected: Entity;
+ id: string;
}
-export function DetailPage({ selected, onBack }: Props): VNode {
+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} valueHandler={null} >
+ <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 />
- <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 />
+ <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>
- {selected.tips && selected.tips.length > 0 ? <Table tips={selected.tips} /> : <div>
- no tips for this reserve
- </div>}
+
+ {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">
@@ -71,6 +105,16 @@ export function DetailPage({ selected, onBack }: Props): VNode {
</div>
}
+function EmptyTable(): VNode {
+ return <div class="content has-text-grey has-text-centered">
+ <p>
+ <span class="icon is-large"><i class="mdi mdi-emoticon-sad mdi-48px" /></span>
+ </p>
+ <p><Translate>No tips has been authorized from this reserve</Translate></p>
+ </div>
+}
+
+
async function copyToClipboard(text: string) {
return navigator.clipboard.writeText(text)
}
@@ -124,4 +168,5 @@ function TipRow({ id, entry }: { id: string, entry: MerchantBackend.Tips.TipStat
<td>{info.reason}</td>
<td>{info.expiration.t_ms === "never" ? "never" : format(info.expiration.t_ms, 'yyyy/MM/dd HH:mm:ss')}</td>
</tr>
-} \ No newline at end of file
+}
+
diff --git a/packages/frontend/src/paths/instance/reserves/details/index.tsx b/packages/frontend/src/paths/instance/reserves/details/index.tsx
index 569ef5b..aaf275b 100644
--- a/packages/frontend/src/paths/instance/reserves/details/index.tsx
+++ b/packages/frontend/src/paths/instance/reserves/details/index.tsx
@@ -34,7 +34,7 @@ interface Props {
onDelete: () => void;
onBack: () => void;
}
-export default function DetailReserve({rid, onUnauthorized, onLoadError, onNotFound, onBack, onDelete}: Props):VNode {
+export default function DetailReserve({ rid, onUnauthorized, onLoadError, onNotFound, onBack, onDelete }: Props): VNode {
const result = useReserveDetails(rid)
if (result.clientError && result.isUnauthorized) return onUnauthorized()
@@ -42,6 +42,6 @@ export default function DetailReserve({rid, onUnauthorized, onLoadError, onNotFo
if (result.loading) return <Loading />
if (!result.ok) return onLoadError(result)
return <Fragment>
- <DetailPage selected={result.data} onBack={onBack} />
+ <DetailPage selected={result.data} onBack={onBack} id={rid} />
</Fragment>
}
diff --git a/packages/frontend/src/paths/instance/reserves/list/Table.tsx b/packages/frontend/src/paths/instance/reserves/list/Table.tsx
index 83e4fb8..c53dd2a 100644
--- a/packages/frontend/src/paths/instance/reserves/list/Table.tsx
+++ b/packages/frontend/src/paths/instance/reserves/list/Table.tsx
@@ -21,10 +21,8 @@
import { format } from "date-fns"
import { Fragment, h, VNode } from "preact"
-import { StateUpdater, useEffect, useState } from "preact/hooks"
import { MerchantBackend, WithId } from "../../../../declaration"
import { Translate } from "../../../../i18n"
-import { Actions, buildActions } from "../../../../utils/table"
type Entity = MerchantBackend.Tips.ReserveStatusEntry & WithId
@@ -54,11 +52,6 @@ export function CardTable({ instances, onCreate, onSelect, onNewTip, onDelete }:
{withoutFunds.length > 0 && <div class="card has-table">
<header class="card-header">
<p class="card-header-title"><span class="icon"><i class="mdi mdi-cash" /></span><Translate>Reserves not yet funded</Translate></p>
- <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>
- </div>
</header>
<div class="card-content">
<div class="b-table has-pagination">
@@ -73,6 +66,11 @@ export function CardTable({ instances, onCreate, onSelect, onNewTip, onDelete }:
<header class="card-header">
<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>
+ </div>
</header>
<div class="card-content">
<div class="b-table has-pagination">
diff --git a/packages/frontend/src/utils/amount.ts b/packages/frontend/src/utils/amount.ts
index 8fdf048..823ad9d 100644
--- a/packages/frontend/src/utils/amount.ts
+++ b/packages/frontend/src/utils/amount.ts
@@ -13,7 +13,7 @@
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/>
*/
-import { Amounts } from "@gnu-taler/taler-util";
+import { amountFractionalBase, AmountJson, Amounts } from "@gnu-taler/taler-util";
import { MerchantBackend } from "../declaration";
/**
@@ -71,8 +71,12 @@ export const subtractPrices = (one: string, two: string) => {
export const rate = (one: string, two: string) => {
const a = Amounts.parseOrThrow(one)
const b = Amounts.parseOrThrow(two)
- const af = Amounts.toFloat(a)
- const bf = Amounts.toFloat(b)
+ const af = toFloat(a)
+ const bf = toFloat(b)
+ if (bf === 0) return 0
return af / bf
}
+function toFloat(amount: AmountJson) {
+ return amount.value + (amount.fraction / amountFractionalBase);
+}