From 0eacc9ad11caa53efe5c71add7028251d058b753 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 7 Jun 2021 09:42:16 -0300 Subject: add more information about reserve --- packages/frontend/src/declaration.d.ts | 38 ++++++++----- .../paths/instance/orders/create/CreatePage.tsx | 25 ++++----- .../reserves/create/CreatedSuccessfully.tsx | 4 +- .../paths/instance/reserves/details/DetailPage.tsx | 63 ++++++++++++++++++---- .../src/paths/instance/reserves/details/index.tsx | 4 +- .../src/paths/instance/reserves/list/Table.tsx | 12 ++--- packages/frontend/src/utils/amount.ts | 10 ++-- 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 { return { inventoryProducts: {}, products: [], @@ -90,8 +90,8 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { const [value, valueHandler] = useState(with_defaults()) // const [errors, setErrors] = useState>({}) - 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 = {} 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 {

} tooltip={i18n`add products to the order that already exist in the inventory`}> @@ -280,7 +281,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { - {value.payments.delivery_date && + {value.payments?.delivery_date && } 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

For example:

-    {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}
     
; } 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
- + name="creation_time" label={i18n`Created at`} readonly /> name="expiration_time" label={i18n`Valid until`} readonly /> name="merchant_initial_amount" label={i18n`Created balance`} readonly /> - name="exchange_initial_amount" label={i18n`Exchange balance`} readonly /> - name="pickup_amount" label={i18n`Picked up`} readonly /> - name="committed_amount" label={i18n`Committed`} readonly /> + name="exchange_url" label={i18n`Exchange URL`} readonly /> + + {didExchangeAckTransfer && + name="exchange_initial_amount" label={i18n`Exchange balance`} readonly /> + name="pickup_amount" label={i18n`Picked up`} readonly /> + name="committed_amount" label={i18n`Committed`} readonly /> + + } + name="payto_uri" label={i18n`Account address`} readonly /> + - {selected.tips && selected.tips.length > 0 ? :
- no tips for this reserve -
} + + {didExchangeAckTransfer ? +
+
+

+ + Tips +

+ +
+
+
+
+ {selected.tips && selected.tips.length > 0 ?
: } + + + + : +

Now you should transfer to the exchange into the account address indicated above and the transaction must carry the subject message.

+ +

For example :

+
+        {selected.payto_uri}?message={id}&amount={selected.merchant_initial_amount}
+      
+
+ } +
@@ -71,6 +105,16 @@ export function DetailPage({ selected, onBack }: Props): VNode {
} +function EmptyTable(): VNode { + return
+

+ +

+

No tips has been authorized from this reserve

+
+} + + async function copyToClipboard(text: string) { return navigator.clipboard.writeText(text) } @@ -124,4 +168,5 @@ function TipRow({ id, entry }: { id: string, entry: MerchantBackend.Tips.TipStat
-} \ 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 if (!result.ok) return onLoadError(result) return - + } 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 &&

Reserves not yet funded

-
- -
@@ -73,6 +66,11 @@ export function CardTable({ instances, onCreate, onSelect, onNewTip, onDelete }:

Reserves ready

+
+ +
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 */ -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); +} -- cgit v1.2.3
{info.reason} {info.expiration.t_ms === "never" ? "never" : format(info.expiration.t_ms, 'yyyy/MM/dd HH:mm:ss')}