[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-merchant-backoffice] 01/02: upgrade timestamp and duration
From: |
gnunet |
Subject: |
[taler-merchant-backoffice] 01/02: upgrade timestamp and duration |
Date: |
Wed, 30 Mar 2022 22:09:50 +0200 |
This is an automated email from the git hooks/post-receive script.
sebasjm pushed a commit to branch master
in repository merchant-backoffice.
commit cf65a507088b90577a32bf0a957becc6096caff4
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Wed Mar 30 17:08:49 2022 -0300
upgrade timestamp and duration
---
packages/merchant-backend/render-examples.ts | 14 +-
packages/merchant-backend/src/declaration.d.ts | 20 +-
packages/merchant-backend/src/hooks/order.ts | 12 +-
.../merchant-backend/src/pages/RequestPayment.tsx | 137 +--
.../src/pages/ShowOrderDetails.examples.ts | 18 +-
.../src/pages/ShowOrderDetails.tsx | 791 ++++++++++-------
packages/merchant-backend/src/utils/amount.ts | 6 +-
.../src/components/form/InputDate.tsx | 10 +-
.../src/components/form/InputDuration.tsx | 202 +++--
.../src/components/form/InputStock.stories.tsx | 160 ++--
.../src/components/product/ProductForm.tsx | 142 +++-
packages/merchant-backoffice/src/declaration.d.ts | 18 +-
packages/merchant-backoffice/src/hooks/order.ts | 4 +-
.../src/paths/admin/create/CreatePage.tsx | 4 +-
.../src/paths/instance/details/DetailPage.tsx | 4 +-
.../src/paths/instance/details/Details.stories.tsx | 45 +-
.../instance/orders/create/Create.stories.tsx | 68 +-
.../paths/instance/orders/create/CreatePage.tsx | 12 +-
.../instance/orders/details/Detail.stories.tsx | 116 +--
.../paths/instance/orders/details/DetailPage.tsx | 931 +++++++++++++--------
.../paths/instance/orders/list/List.stories.tsx | 138 +--
.../src/paths/instance/orders/list/Table.tsx | 434 +++++++---
.../src/paths/instance/products/list/Table.tsx | 548 ++++++++----
.../paths/instance/reserves/details/DetailPage.tsx | 4 +-
.../instance/reserves/details/Details.stories.tsx | 101 +--
.../instance/reserves/list/CreatedSuccessfully.tsx | 104 ++-
.../paths/instance/reserves/list/List.stories.tsx | 123 +--
.../src/paths/instance/reserves/list/Table.tsx | 339 +++++---
.../paths/instance/transfers/list/List.stories.tsx | 110 +--
.../src/paths/instance/transfers/list/Table.tsx | 230 +++--
.../src/paths/instance/update/Update.stories.tsx | 44 +-
.../src/paths/instance/update/UpdatePage.tsx | 4 +-
packages/merchant-backoffice/src/schemas/index.ts | 4 +-
packages/merchant-backoffice/src/utils/amount.ts | 6 +-
.../tests/hooks/swr/reserve.test.ts | 4 +-
35 files changed, 3068 insertions(+), 1839 deletions(-)
diff --git a/packages/merchant-backend/render-examples.ts
b/packages/merchant-backend/render-examples.ts
index c1da3ce..47300ab 100644
--- a/packages/merchant-backend/render-examples.ts
+++ b/packages/merchant-backend/render-examples.ts
@@ -58,23 +58,23 @@ files.forEach(file => {
//enhance the example with more information
example.contract_terms_json = () =>
JSON.stringify(example.contract_terms);
- example.contract_terms.timestamp_str = () =>
example.contract_terms.timestamp &&
format(example.contract_terms.timestamp.t_ms, 'dd MMM yyyy HH:mm:ss');
+ example.contract_terms.timestamp_str = () =>
example.contract_terms.timestamp &&
format(example.contract_terms.timestamp.t_s, 'dd MMM yyyy HH:mm:ss');
example.contract_terms.hasProducts = () =>
example.contract_terms.products?.length > 0;
example.contract_terms.hasAuditors = () =>
example.contract_terms.auditors?.length > 0;
example.contract_terms.hasExchanges = () =>
example.contract_terms.exchanges?.length > 0;
example.contract_terms.products.forEach(p => {
- p.delivery_date_str = () => p.delivery_date &&
format(p.delivery_date.t_ms, 'dd MM yyyy HH:mm:ss')
+ p.delivery_date_str = () => p.delivery_date &&
format(p.delivery_date.t_s, 'dd MM yyyy HH:mm:ss')
p.hasTaxes = () => p.taxes?.length > 0
})
example.contract_terms.has_delivery_info = () =>
example.contract_terms.delivery_date || example.contract_terms.delivery_location
- example.contract_terms.delivery_date_str = () =>
example.contract_terms.delivery_date &&
format(example.contract_terms.delivery_date.t_ms, 'dd MM yyyy HH:mm:ss')
- example.contract_terms.pay_deadline_str = () =>
example.contract_terms.pay_deadline &&
format(example.contract_terms.pay_deadline.t_ms, 'dd MM yyyy HH:mm:ss')
- example.contract_terms.wire_transfer_deadline_str = () =>
example.contract_terms.wire_transfer_deadline &&
format(example.contract_terms.wire_transfer_deadline.t_ms, 'dd MM yyyy
HH:mm:ss')
- example.contract_terms.refund_deadline_str = () =>
example.contract_terms.refund_deadline &&
format(example.contract_terms.refund_deadline.t_ms, 'dd MM yyyy HH:mm:ss')
- example.contract_terms.auto_refund_str = () =>
example.contract_terms.auto_refund && formatDuration(intervalToDuration({
start: 0, end: example.contract_terms.auto_refund.d_ms }))
+ example.contract_terms.delivery_date_str = () =>
example.contract_terms.delivery_date &&
format(example.contract_terms.delivery_date.t_s, 'dd MM yyyy HH:mm:ss')
+ example.contract_terms.pay_deadline_str = () =>
example.contract_terms.pay_deadline &&
format(example.contract_terms.pay_deadline.t_s, 'dd MM yyyy HH:mm:ss')
+ example.contract_terms.wire_transfer_deadline_str = () =>
example.contract_terms.wire_transfer_deadline &&
format(example.contract_terms.wire_transfer_deadline.t_s, 'dd MM yyyy HH:mm:ss')
+ example.contract_terms.refund_deadline_str = () =>
example.contract_terms.refund_deadline &&
format(example.contract_terms.refund_deadline.t_s, 'dd MM yyyy HH:mm:ss')
+ example.contract_terms.auto_refund_str = () =>
example.contract_terms.auto_refund && formatDuration(intervalToDuration({
start: 0, end: example.contract_terms.auto_refund.d_us }))
const output = mustache.render(html, example);
diff --git a/packages/merchant-backend/src/declaration.d.ts
b/packages/merchant-backend/src/declaration.d.ts
index 1722a3d..74b0a50 100644
--- a/packages/merchant-backend/src/declaration.d.ts
+++ b/packages/merchant-backend/src/declaration.d.ts
@@ -35,12 +35,12 @@ interface Timestamp {
// Milliseconds since epoch, or the special
// value "forever" to represent an event that will
// never happen.
- t_ms: number | "never";
+ t_s: number | "never";
}
interface Duration {
// Duration in milliseconds or "forever"
// to represent an infinite duration.
- d_ms: number | "forever";
+ d_us: number | "forever";
}
interface WithId {
@@ -83,12 +83,12 @@ export namespace ExchangeBackend {
// 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;
+ start_date: TalerProtocolTimestamp;
// 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;
+ end_date: TalerProtocolTimestamp;
// Signature of TALER_MasterWireFeePS with
// purpose TALER_SIGNATURE_MASTER_WIRE_FEES.
@@ -1040,7 +1040,7 @@ export namespace MerchantBackend {
reason: string;
// Timestamp indicating when the tip is set to expire (may be in
the past).
- expiration: Timestamp;
+ expiration: TalerProtocolTimestamp;
// Reserve public key from which the tip is funded.
reserve_pub: EddsaPublicKey;
@@ -1307,17 +1307,17 @@ export namespace MerchantBackend {
products: Product[];
// Time when this contract was generated
- timestamp: Timestamp;
+ timestamp: TalerProtocolTimestamp;
// After this deadline has passed, no refunds will be accepted.
- refund_deadline: Timestamp;
+ refund_deadline: TalerProtocolTimestamp;
// After this deadline, the merchant won't accept payments for the
contact
- pay_deadline: Timestamp;
+ pay_deadline: TalerProtocolTimestamp;
// Transfer deadline for the exchange. Must be in the
// deposit permissions of coins used to pay for this order.
- wire_transfer_deadline: Timestamp;
+ wire_transfer_deadline: TalerProtocolTimestamp;
// Merchant's public key used to sign this proposal; this information
// is typically added by the backend Note that this can be an
ephemeral key.
@@ -1350,7 +1350,7 @@ export namespace MerchantBackend {
// Time indicating when the order should be delivered.
// May be overwritten by individual products.
- delivery_date?: Timestamp;
+ delivery_date?: TalerProtocolTimestamp;
// Nonce generated by the wallet and echoed by the merchant
// in this field when the proposal is generated.
diff --git a/packages/merchant-backend/src/hooks/order.ts
b/packages/merchant-backend/src/hooks/order.ts
index a0d7916..4a17eac 100644
--- a/packages/merchant-backend/src/hooks/order.ts
+++ b/packages/merchant-backend/src/hooks/order.ts
@@ -118,7 +118,7 @@ export function useOrderDetails(oderId: string):
HttpResponse<MerchantBackend.Or
};
const { data, error, isValidating } =
useSWR<HttpResponseOk<MerchantBackend.Orders.MerchantOrderStatusResponse>,
HttpError>([`/private/orders/${oderId}`, token, url], fetcher, {
- refreshInterval:0,
+ refreshInterval: 0,
refreshWhenHidden: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
@@ -181,8 +181,8 @@ export function useInstanceOrders(args?:
InstanceOrderFilter, updateFilter?: (d:
if (beforeError) return beforeError
if (afterError) return afterError
-
-
+
+
const pagination = {
isReachingEnd: afterData && afterData.data.orders.length < totalAfter,
isReachingStart: (!args?.date) || (beforeData &&
beforeData.data.orders.length < totalBefore),
@@ -191,7 +191,7 @@ export function useInstanceOrders(args?:
InstanceOrderFilter, updateFilter?: (d:
if (afterData.data.orders.length < MAX_RESULT_SIZE) {
setPageAfter(pageAfter + 1)
} else {
- const from = afterData.data.orders[afterData.data.orders.length -
1].timestamp.t_ms
+ const from = afterData.data.orders[afterData.data.orders.length -
1].timestamp.t_s
if (from && updateFilter) updateFilter(new Date(from))
}
},
@@ -200,12 +200,12 @@ export function useInstanceOrders(args?:
InstanceOrderFilter, updateFilter?: (d:
if (beforeData.data.orders.length < MAX_RESULT_SIZE) {
setPageBefore(pageBefore + 1)
} else if (beforeData) {
- const from = beforeData.data.orders[beforeData.data.orders.length -
1].timestamp.t_ms
+ const from = beforeData.data.orders[beforeData.data.orders.length -
1].timestamp.t_s
if (from && updateFilter) updateFilter(new Date(from))
}
},
}
-
+
const orders = !beforeData || !afterData ? [] : (beforeData ||
lastBefore).data.orders.slice().reverse().concat((afterData ||
lastAfter).data.orders)
if (loadingAfter || loadingBefore) return { loading: true, data: { orders } }
if (beforeData && afterData) {
diff --git a/packages/merchant-backend/src/pages/RequestPayment.tsx
b/packages/merchant-backend/src/pages/RequestPayment.tsx
index 8dd22d1..050755d 100644
--- a/packages/merchant-backend/src/pages/RequestPayment.tsx
+++ b/packages/merchant-backend/src/pages/RequestPayment.tsx
@@ -15,64 +15,73 @@
*/
/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-import { Fragment, h, render, VNode } from 'preact';
-import { render as renderToString } from 'preact-render-to-string';
-import { useEffect } from 'preact/hooks';
-import { Footer } from '../components/Footer';
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+import { Fragment, h, render, VNode } from "preact";
+import { render as renderToString } from "preact-render-to-string";
+import { useEffect } from "preact/hooks";
+import { Footer } from "../components/Footer";
import "../css/pure-min.css";
import "../css/style.css";
-import { QR } from '../components/QR';
-import { Page, QRPlaceholder, WalletLink } from '../styled';
-
+import { QR } from "../components/QR";
+import { Page, QRPlaceholder, WalletLink } from "../styled";
/**
* This page creates a payment request QR code
- *
+ *
* It will build into a mustache html template for server side rendering
- *
+ *
* server side rendering params:
* - order_status_url
* - taler_pay_qrcode_svg
* - taler_pay_uri
* - order_summary
- *
+ *
* request params:
* - pay_uri
* - order_summary
* - order_status_url
*/
-
interface Props {
- payURI?: string,
- order_status_url?: string,
- qr_code?: string,
+ payURI?: string;
+ order_status_url?: string;
+ qr_code?: string;
}
function Head({ order_summary }: { order_summary?: string }): VNode {
- return <Fragment>
- <meta charSet="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <noscript>
- <meta http-equiv="refresh" content="1" />
- </noscript>
- <title>Payment requested for {order_summary ? order_summary : `{{
order_summary }}`}</title>
- </Fragment>
+ return (
+ <Fragment>
+ <meta charSet="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <noscript>
+ <meta http-equiv="refresh" content="1" />
+ </noscript>
+ <title>
+ Payment requested for{" "}
+ {order_summary ? order_summary : `{{ order_summary }}`}
+ </title>
+ </Fragment>
+ );
}
-export function RequestPayment({ payURI, qr_code, order_status_url }: Props):
VNode {
+export function RequestPayment({
+ payURI,
+ qr_code,
+ order_status_url,
+}: Props): VNode {
useEffect(() => {
const longpollDelayMs = 60 * 1000;
let checkUrl: URL;
try {
- checkUrl = new URL(order_status_url ? order_status_url : "{{&
order_status_url }}");
+ checkUrl = new URL(
+ order_status_url ? order_status_url : "{{& order_status_url }}"
+ );
} catch (e) {
return;
}
- checkUrl.searchParams.set("timeout_ms", longpollDelayMs.toString());
+ checkUrl.searchParams.set("timeout_s", longpollDelayMs.toString());
function check() {
let retried = false;
function retryOnce() {
@@ -119,53 +128,58 @@ export function RequestPayment({ payURI, qr_code,
order_status_url }: Props): VN
};
req.onerror = function () {
setTimeout(retryOnce, 500);
- }
+ };
req.ontimeout = function () {
setTimeout(retryOnce, 500);
- }
+ };
req.timeout = longpollDelayMs;
req.open("GET", checkUrl.href);
req.send();
}
setTimeout(check, 500);
- })
- return <Page>
- <section>
- <h1 >Pay with Taler</h1>
- <p>
- Scan this QR code with your mobile wallet:
- </p>
- <QRPlaceholder dangerouslySetInnerHTML={{ __html: qr_code ? qr_code :
`{{{ taler_pay_qrcode_svg }}}` }} />
- <p>
- <WalletLink href={payURI ? payURI : `{{ taler_pay_uri }}`}>
- Or open your Taller wallet
- </WalletLink>
- </p>
- <p>
- <a href="https://wallet.taler.net/">Don't have a Taler wallet yet?
Install it!</a>
- </p>
- </section>
- <Footer />
- </Page>
-
+ });
+ return (
+ <Page>
+ <section>
+ <h1>Pay with Taler</h1>
+ <p>Scan this QR code with your mobile wallet:</p>
+ <QRPlaceholder
+ dangerouslySetInnerHTML={{
+ __html: qr_code ? qr_code : `{{{ taler_pay_qrcode_svg }}}`,
+ }}
+ />
+ <p>
+ <WalletLink href={payURI ? payURI : `{{ taler_pay_uri }}`}>
+ Or open your Taller wallet
+ </WalletLink>
+ </p>
+ <p>
+ <a href="https://wallet.taler.net/">
+ Don't have a Taler wallet yet? Install it!
+ </a>
+ </p>
+ </section>
+ <Footer />
+ </Page>
+ );
}
export function mount(): void {
try {
- const fromLocation = new URL(window.location.href).searchParams
- const os = fromLocation.get('order_summary') || undefined;
+ const fromLocation = new URL(window.location.href).searchParams;
+ const os = fromLocation.get("order_summary") || undefined;
if (os) {
render(<Head order_summary={os} />, document.head);
}
- const uri = fromLocation.get('pay_uri') || undefined;
- const osu = fromLocation.get('order_status_url') || undefined;
+ const uri = fromLocation.get("pay_uri") || undefined;
+ const osu = fromLocation.get("order_status_url") || undefined;
const qr_code = uri ? renderToString(<QR text={uri} />) : undefined;
- render(<RequestPayment
- payURI={uri} order_status_url={osu}
- qr_code={qr_code}
- />, document.body);
+ render(
+ <RequestPayment payURI={uri} order_status_url={osu} qr_code={qr_code} />,
+ document.body
+ );
} catch (e) {
console.error("got error", e);
if (e instanceof Error) {
@@ -174,10 +188,9 @@ export function mount(): void {
}
}
-export function buildTimeRendering(): { head: string, body: string } {
+export function buildTimeRendering(): { head: string; body: string } {
return {
head: renderToString(<Head />),
- body: renderToString(<RequestPayment />)
- }
+ body: renderToString(<RequestPayment />),
+ };
}
-
diff --git a/packages/merchant-backend/src/pages/ShowOrderDetails.examples.ts
b/packages/merchant-backend/src/pages/ShowOrderDetails.examples.ts
index 8d76900..ba68397 100644
--- a/packages/merchant-backend/src/pages/ShowOrderDetails.examples.ts
+++ b/packages/merchant-backend/src/pages/ShowOrderDetails.examples.ts
@@ -28,7 +28,7 @@ const defaultContractTerms: MerchantBackend.ContractTerms = {
amount: 'USD:10',
summary: 'this is a short summary',
pay_deadline: {
- t_ms: new Date().getTime() + 6 * 24 * 60 * 60 * 1000
+ t_s: new Date().getTime() + 6 * 24 * 60 * 60 * 1000
},
merchant: {
name: 'the merchant (inc)',
@@ -48,7 +48,7 @@ const defaultContractTerms: MerchantBackend.ContractTerms = {
wire_fee_amortization: 1,
products: [],
timestamp: {
- t_ms: new Date().getTime()
+ t_s: new Date().getTime()
},
auditors: [],
exchanges: [],
@@ -57,11 +57,11 @@ const defaultContractTerms: MerchantBackend.ContractTerms =
{
merchant_pub: 'QWEASDQWEASD',
nonce: 'NONCE',
refund_deadline: {
- t_ms: new Date().getTime() + 6 * 24 * 60 * 60 * 1000
+ t_s: new Date().getTime() + 6 * 24 * 60 * 60 * 1000
},
wire_method: 'x-taler-bank',
wire_transfer_deadline: {
- t_ms: new Date().getTime() + 3 * 24 * 60 * 60 * 1000
+ t_s: new Date().getTime() + 3 * 24 * 60 * 60 * 1000
},
};
@@ -85,7 +85,7 @@ export const exampleData: { [name: string]: Props } = {
contract_terms: {
...defaultContractTerms,
delivery_date: {
- t_ms: inSixDays
+ t_s: inSixDays
},
},
},
@@ -124,7 +124,7 @@ export const exampleData: { [name: string]: Props } = {
town_location: 'town loc',
},
delivery_date: {
- t_ms: inSixDays
+ t_s: inSixDays
},
},
},
@@ -136,7 +136,7 @@ export const exampleData: { [name: string]: Props } = {
description: 'description of the first product',
price: '5:USD',
quantity: 1,
- delivery_date: { t_ms: in10Minutes },
+ delivery_date: { t_s: in10Minutes },
product_id: '12333',
}, {
description: 'another description',
@@ -159,7 +159,7 @@ export const exampleData: { [name: string]: Props } = {
price: '5:USD',
quantity: 1,
unit: 'beer',
- delivery_date: { t_ms: in10Minutes },
+ delivery_date: { t_s: in10Minutes },
product_id: '456',
taxes: [{
name: 'VAT', tax: 'USD:1'
@@ -212,7 +212,7 @@ export const exampleData: { [name: string]: Props } = {
contract_terms: {
...defaultContractTerms,
auto_refund: {
- d_ms: 1000 * 60 * 60 * 26 + 1000 * 60 * 30
+ d_us: 1000 * 60 * 60 * 26 + 1000 * 60 * 30
}
},
},
diff --git a/packages/merchant-backend/src/pages/ShowOrderDetails.tsx
b/packages/merchant-backend/src/pages/ShowOrderDetails.tsx
index 43b730c..aa62c29 100644
--- a/packages/merchant-backend/src/pages/ShowOrderDetails.tsx
+++ b/packages/merchant-backend/src/pages/ShowOrderDetails.tsx
@@ -15,32 +15,32 @@
*/
/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-import { format, formatDuration } from 'date-fns';
-import { intervalToDuration } from 'date-fns/esm';
-import { Fragment, h, render, VNode } from 'preact';
-import { render as renderToString } from 'preact-render-to-string';
-import { Footer } from '../components/Footer';
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+import { format, formatDuration } from "date-fns";
+import { intervalToDuration } from "date-fns/esm";
+import { Fragment, h, render, VNode } from "preact";
+import { render as renderToString } from "preact-render-to-string";
+import { Footer } from "../components/Footer";
import "../css/pure-min.css";
import "../css/style.css";
-import { MerchantBackend } from '../declaration';
-import { Page, InfoBox, TableExpanded, TableSimple } from '../styled';
+import { MerchantBackend } from "../declaration";
+import { Page, InfoBox, TableExpanded, TableSimple } from "../styled";
/**
* This page creates a payment request QR code
- *
+ *
* It will build into a mustache html template for server side rendering
- *
+ *
* server side rendering params:
* - order_summary
* - contract_terms
* - refund_amount
- *
+ *
* request params:
* - refund_amount
- * - contract_terms
+ * - contract_terms
* - order_summary
*/
@@ -52,332 +52,489 @@ export interface Props {
}
function Head({ order_summary }: { order_summary?: string }): VNode {
- return <Fragment>
- <meta charSet="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <noscript>
- <meta http-equiv="refresh" content="1" />
- </noscript>
- <title>Status of your order for {order_summary ? order_summary : `{{
order_summary }}`}</title>
- <script>{`
+ return (
+ <Fragment>
+ <meta charSet="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <noscript>
+ <meta http-equiv="refresh" content="1" />
+ </noscript>
+ <title>
+ Status of your order for{" "}
+ {order_summary ? order_summary : `{{ order_summary }}`}
+ </title>
+ <script>{`
var contractTermsStr = '{{{contract_terms_json}}}';
`}</script>
- </Fragment>
+ </Fragment>
+ );
}
-function Location({ templateName, location, btr }: { templateName: string,
location: MerchantBackend.Location | undefined, btr?: boolean }) {
- //FIXME: mustache strings show be constructed in a way that ends in the
final output of the html but is not present in the
+function Location({
+ templateName,
+ location,
+ btr,
+}: {
+ templateName: string;
+ location: MerchantBackend.Location | undefined;
+ btr?: boolean;
+}) {
+ //FIXME: mustache strings show be constructed in a way that ends in the
final output of the html but is not present in the
// javascript code, otherwise when mustache render engine run over the html
it will also replace string in the javascript code
// that is made to run when the browser has javascript enable leading into
undefined behavior.
// that's why in the next fields we are using concatenations to build the
mustache placeholder.
- return <Fragment>
- {btr && `{{`+`#${templateName}.building_name}}`}
- <dd>{location?.building_name || (btr && `{{ ${templateName}.building_name
}}`)} {location?.building_number || (btr && `{{ ${templateName}.building_number
}}`)}</dd>
- {btr && `{{`+`/${templateName}.building_name}}`}
-
- {btr && `{{`+`#${templateName}.country}}`}
- <dd>{location?.country || (btr && `{{ ${templateName}.country }}`)}
{location?.country_subdivision || (btr && `{{
${templateName}.country_subdivision }}`)}</dd>
- {btr && `{{`+`/${templateName}.country}}`}
-
- {btr && `{{`+`#${templateName}.district}}`}
- <dd>{location?.district || (btr && `{{ ${templateName}.district }}`)}</dd>
- {btr && `{{`+`/${templateName}.district}}`}
-
- {btr && `{{`+`#${templateName}.post_code}}`}
- <dd>{location?.post_code || (btr && `{{ ${templateName}.post_code
}}`)}</dd>
- {btr && `{{`+`/${templateName}.post_code}}`}
-
- {btr && `{{`+`#${templateName}.street}}`}
- <dd>{location?.street || (btr && `{{ ${templateName}.street }}`)}</dd>
- {btr && `{{`+`/${templateName}.street}}`}
-
- {btr && `{{`+`#${templateName}.town}}`}
- <dd>{location?.town || (btr && `{{ ${templateName}.town }}`)}</dd>
- {btr && `{{`+`/${templateName}.town}}`}
-
- {btr && `{{`+`#${templateName}.town_location}}`}
- <dd>{location?.town_location || (btr && `{{ ${templateName}.town_location
}}`)}</dd>
- {btr && `{{`+`/${templateName}.town_location}}`}
- </Fragment>
+ return (
+ <Fragment>
+ {btr && `{{` + `#${templateName}.building_name}}`}
+ <dd>
+ {location?.building_name ||
+ (btr && `{{ ${templateName}.building_name }}`)}{" "}
+ {location?.building_number ||
+ (btr && `{{ ${templateName}.building_number }}`)}
+ </dd>
+ {btr && `{{` + `/${templateName}.building_name}}`}
+
+ {btr && `{{` + `#${templateName}.country}}`}
+ <dd>
+ {location?.country || (btr && `{{ ${templateName}.country }}`)}{" "}
+ {location?.country_subdivision ||
+ (btr && `{{ ${templateName}.country_subdivision }}`)}
+ </dd>
+ {btr && `{{` + `/${templateName}.country}}`}
+
+ {btr && `{{` + `#${templateName}.district}}`}
+ <dd>{location?.district || (btr && `{{ ${templateName}.district
}}`)}</dd>
+ {btr && `{{` + `/${templateName}.district}}`}
+
+ {btr && `{{` + `#${templateName}.post_code}}`}
+ <dd>
+ {location?.post_code || (btr && `{{ ${templateName}.post_code }}`)}
+ </dd>
+ {btr && `{{` + `/${templateName}.post_code}}`}
+
+ {btr && `{{` + `#${templateName}.street}}`}
+ <dd>{location?.street || (btr && `{{ ${templateName}.street }}`)}</dd>
+ {btr && `{{` + `/${templateName}.street}}`}
+
+ {btr && `{{` + `#${templateName}.town}}`}
+ <dd>{location?.town || (btr && `{{ ${templateName}.town }}`)}</dd>
+ {btr && `{{` + `/${templateName}.town}}`}
+
+ {btr && `{{` + `#${templateName}.town_location}}`}
+ <dd>
+ {location?.town_location ||
+ (btr && `{{ ${templateName}.town_location }}`)}
+ </dd>
+ {btr && `{{` + `/${templateName}.town_location}}`}
+ </Fragment>
+ );
}
-export function ShowOrderDetails({ order_summary, refund_amount,
contract_terms, btr }: Props): VNode {
- const productList = (btr ? [{} as MerchantBackend.Product] :
(contract_terms?.products || []))
- const auditorsList = (btr ? [{} as MerchantBackend.Auditor] :
(contract_terms?.auditors || []))
- const exchangesList = (btr ? [{} as MerchantBackend.Exchange] :
(contract_terms?.exchanges || []))
- const hasDeliveryInfo = btr || !!contract_terms?.delivery_date ||
!!contract_terms?.delivery_location
-
- return <Page>
- <header>
- <h1>Details of order {contract_terms?.order_id || `{{
contract_terms.order_id }}`}</h1>
- </header>
-
- <section>
- {btr && `{{#refund_amount}}`}
- {(btr || refund_amount) && <section>
- <InfoBox>
- <b>Refunded:</b> The merchant refunded you <b>{refund_amount || `{{
refund_amount }}`}</b>.
- </InfoBox>
- </section>}
- {btr && `{{/refund_amount}}`}
-
- <section>
- <TableExpanded>
- <dt>Order summary:</dt>
- <dd>{contract_terms?.summary || `{{ contract_terms.summary }}`}</dd>
- <dt>Amount paid:</dt>
- <dd>{contract_terms?.amount || `{{ contract_terms.amount }}`}</dd>
- <dt>Order date:</dt>
- <dd>{contract_terms?.timestamp ?
- (contract_terms?.timestamp.t_ms != 'never' ?
- format(contract_terms?.timestamp.t_ms, 'dd MMM yyyy HH:mm:ss') :
- 'never')
- : `{{ contract_terms.timestamp_str }}`} </dd>
- <dt>Merchant name:</dt>
- <dd>{contract_terms?.merchant.name || `{{
contract_terms.merchant.name }}`}</dd>
- </TableExpanded>
- </section>
-
- {btr && `{{#contract_terms.hasProducts}}`}
- {!productList.length ? null : <section>
- <h2>Products purchased</h2>
- <TableSimple>
- {btr && '{{' + '#contract_terms.products' + '}}'}
- {productList.map((p, i) => {
- const taxList = (btr ? [{} as MerchantBackend.Tax] : (p.taxes ||
[]))
-
- return <Fragment key={i}>
- <p>{p.description || `{{description}}`}</p>
- <dl>
- <dt>Quantity:</dt>
- <dd>{p.quantity || `{{quantity}}`}</dd>
-
- <dt>Price:</dt>
- <dd>{p.price || `{{price}}`}</dd>
-
- {btr && `{{#hasTaxes}}`}
- {!taxList.length ? null : <Fragment>
- {btr && '{{' + '#taxes' + '}}'}
- {taxList.map((t, i) => {
- return <Fragment key={i}>
- <dt>{t.name || `{{name}}`}</dt>
- <dd>{t.tax || `{{tax}}`}</dd>
- </Fragment>
- })}
- {btr && '{{' + '/taxes' + '}}'}
- </Fragment>}
- {btr && `{{/hasTaxes}}`}
-
- {btr && `{{#delivery_date}}`}
- {(btr || p.delivery_date) && <Fragment>
- <dt>Delivered on:</dt>
- <dd>{p.delivery_date ?
- (p.delivery_date.t_ms != 'never' ?
- format(p.delivery_date.t_ms, 'dd MMM yyyy HH:mm:ss') :
- 'never')
- : `{{ delivery_date_str }}`} </dd>
- </Fragment>}
- {btr && `{{/delivery_date}}`}
-
- {btr && `{{#unit}}`}
- {(btr || p.unit) && <Fragment>
- <dt>Product unit:</dt>
- <dd>{p.unit || `{{.}}`}</dd>
- </Fragment>}
- {btr && `{{/unit}}`}
-
- {btr && `{{#product_id}}`}
- {(btr || p.product_id) && <Fragment>
- <dt>Product ID:</dt>
- <dd>{p.product_id || `{{.}}`}</dd>
- </Fragment>}
- {btr && `{{/product_id}}`}
-
- </dl>
- </Fragment>
- })}
- {btr && '{{' + '/contract_terms.products' + '}}'}
- </TableSimple>
-
- </section>}
- {btr && `{{/contract_terms.hasProducts}}`}
-
-
- {btr && `{{#contract_terms.has_delivery_info}}`}
- {!hasDeliveryInfo ? null : <section>
- <h2>Delivery information</h2>
- <TableExpanded>
- {btr && `{{#contract_terms.delivery_date}}`}
- {(btr || contract_terms?.delivery_date) && <Fragment>
- <dt>Delivery date:</dt>
- <dd>{contract_terms?.delivery_date ?
- (contract_terms?.delivery_date.t_ms != 'never' ?
- format(contract_terms?.delivery_date.t_ms, 'dd MMM yyyy
HH:mm:ss') :
- 'never')
- : `{{ contract_terms.delivery_date_str }}`} </dd>
-
- </Fragment>}
- {btr && `{{/contract_terms.delivery_date}}`}
-
- {btr && `{{#contract_terms.delivery_location}}`}
- {(btr || contract_terms?.delivery_location) && <Fragment>
- <dt>Delivery address:</dt>
- <Location btr={btr} location={contract_terms?.delivery_location}
templateName="contract_terms.delivery_location" />
- </Fragment>}
- {btr && `{{/contract_terms.delivery_location}}`}
- </TableExpanded>
- </section>}
- {btr && `{{/contract_terms.has_delivery_info}}`}
+export function ShowOrderDetails({
+ order_summary,
+ refund_amount,
+ contract_terms,
+ btr,
+}: Props): VNode {
+ const productList = btr
+ ? [{} as MerchantBackend.Product]
+ : contract_terms?.products || [];
+ const auditorsList = btr
+ ? [{} as MerchantBackend.Auditor]
+ : contract_terms?.auditors || [];
+ const exchangesList = btr
+ ? [{} as MerchantBackend.Exchange]
+ : contract_terms?.exchanges || [];
+ const hasDeliveryInfo =
+ btr ||
+ !!contract_terms?.delivery_date ||
+ !!contract_terms?.delivery_location;
+
+ return (
+ <Page>
+ <header>
+ <h1>
+ Details of order{" "}
+ {contract_terms?.order_id || `{{ contract_terms.order_id }}`}
+ </h1>
+ </header>
<section>
- <h2>Full payment information</h2>
- <TableExpanded>
- <dt>Amount paid:</dt>
- <dd>{contract_terms?.amount || `{{ contract_terms.amount }}`}</dd>
- <dt>Wire transfer method:</dt>
- <dd>{contract_terms?.wire_method || `{{ contract_terms.wire_method
}}`}</dd>
- <dt>Payment deadline:</dt>
- <dd>{contract_terms?.pay_deadline ?
- (contract_terms?.pay_deadline.t_ms != 'never' ?
- format(contract_terms?.pay_deadline.t_ms, 'dd MMM yyyy
HH:mm:ss') :
- 'never')
- : `{{ contract_terms.pay_deadline_str }}`} </dd>
- <dt>Exchange transfer deadline:</dt>
- <dd>{contract_terms?.wire_transfer_deadline ?
- (contract_terms?.wire_transfer_deadline.t_ms != 'never' ?
- format(contract_terms?.wire_transfer_deadline.t_ms, 'dd MMM yyyy
HH:mm:ss') :
- 'never')
- : `{{ contract_terms.wire_transfer_deadline_str }}`} </dd>
- <dt>Maximum deposit fee:</dt>
- <dd>{contract_terms?.max_fee || `{{ contract_terms.max_fee }}`}</dd>
- <dt>Maximum wire fee:</dt>
- <dd>{contract_terms?.max_wire_fee || `{{ contract_terms.max_wire_fee
}}`}</dd>
- <dt>Wire fee amortization:</dt>
- <dd>{contract_terms?.wire_fee_amortization || `{{
contract_terms.wire_fee_amortization }}`} transactions</dd>
- </TableExpanded>
- </section>
-
- <section>
- <h2>Refund information</h2>
- <TableExpanded>
- <dt>Refund deadline:</dt>
- <dd>{contract_terms?.refund_deadline ?
- (contract_terms?.refund_deadline.t_ms != 'never' ?
- format(contract_terms?.refund_deadline.t_ms, 'dd MMM yyyy
HH:mm:ss') :
- 'never')
- : `{{ contract_terms.refund_deadline_str }}`} </dd>
-
- {btr && `{{#contract_terms.auto_refund}}`}
- {(btr || contract_terms?.auto_refund) && <Fragment>
- <dt>Attempt autorefund for:</dt>
- <dd>{contract_terms?.auto_refund ?
- (contract_terms?.auto_refund.d_ms != 'forever' ?
- formatDuration(intervalToDuration({ start: 0, end:
contract_terms?.auto_refund.d_ms })) :
- 'forever')
- : `{{ contract_terms.auto_refund_str }}`} </dd>
- </Fragment>}
- {btr && `{{/contract_terms.auto_refund}}`}
- </TableExpanded>
- </section>
-
- <section>
- <h2>Additional order details</h2>
- <TableExpanded>
- <dt>Public reorder URL:</dt>
- <dd> -- not defined yet -- </dd>
- {btr && `{{#contract_terms.fulfillment_url}}`}
- {(btr || contract_terms?.fulfillment_url) && <Fragment>
- <dt>Fulfillment URL:</dt>
- <dd>{contract_terms?.fulfillment_url || (btr && `{{
contract_terms.fulfillment_url }}`)}</dd>
- </Fragment>}
- {btr && `{{/contract_terms.fulfillment_url}}`}
- {/* <dt>Fulfillment message:</dt>
+ {btr && `{{#refund_amount}}`}
+ {(btr || refund_amount) && (
+ <section>
+ <InfoBox>
+ <b>Refunded:</b> The merchant refunded you{" "}
+ <b>{refund_amount || `{{ refund_amount }}`}</b>.
+ </InfoBox>
+ </section>
+ )}
+ {btr && `{{/refund_amount}}`}
+
+ <section>
+ <TableExpanded>
+ <dt>Order summary:</dt>
+ <dd>{contract_terms?.summary || `{{ contract_terms.summary
}}`}</dd>
+ <dt>Amount paid:</dt>
+ <dd>{contract_terms?.amount || `{{ contract_terms.amount }}`}</dd>
+ <dt>Order date:</dt>
+ <dd>
+ {contract_terms?.timestamp
+ ? contract_terms?.timestamp.t_s != "never"
+ ? format(
+ contract_terms?.timestamp.t_s,
+ "dd MMM yyyy HH:mm:ss"
+ )
+ : "never"
+ : `{{ contract_terms.timestamp_str }}`}{" "}
+ </dd>
+ <dt>Merchant name:</dt>
+ <dd>
+ {contract_terms?.merchant.name ||
+ `{{ contract_terms.merchant.name }}`}
+ </dd>
+ </TableExpanded>
+ </section>
+
+ {btr && `{{#contract_terms.hasProducts}}`}
+ {!productList.length ? null : (
+ <section>
+ <h2>Products purchased</h2>
+ <TableSimple>
+ {btr && "{{" + "#contract_terms.products" + "}}"}
+ {productList.map((p, i) => {
+ const taxList = btr
+ ? [{} as MerchantBackend.Tax]
+ : p.taxes || [];
+
+ return (
+ <Fragment key={i}>
+ <p>{p.description || `{{description}}`}</p>
+ <dl>
+ <dt>Quantity:</dt>
+ <dd>{p.quantity || `{{quantity}}`}</dd>
+
+ <dt>Price:</dt>
+ <dd>{p.price || `{{price}}`}</dd>
+
+ {btr && `{{#hasTaxes}}`}
+ {!taxList.length ? null : (
+ <Fragment>
+ {btr && "{{" + "#taxes" + "}}"}
+ {taxList.map((t, i) => {
+ return (
+ <Fragment key={i}>
+ <dt>{t.name || `{{name}}`}</dt>
+ <dd>{t.tax || `{{tax}}`}</dd>
+ </Fragment>
+ );
+ })}
+ {btr && "{{" + "/taxes" + "}}"}
+ </Fragment>
+ )}
+ {btr && `{{/hasTaxes}}`}
+
+ {btr && `{{#delivery_date}}`}
+ {(btr || p.delivery_date) && (
+ <Fragment>
+ <dt>Delivered on:</dt>
+ <dd>
+ {p.delivery_date
+ ? p.delivery_date.t_s != "never"
+ ? format(
+ p.delivery_date.t_s,
+ "dd MMM yyyy HH:mm:ss"
+ )
+ : "never"
+ : `{{ delivery_date_str }}`}{" "}
+ </dd>
+ </Fragment>
+ )}
+ {btr && `{{/delivery_date}}`}
+
+ {btr && `{{#unit}}`}
+ {(btr || p.unit) && (
+ <Fragment>
+ <dt>Product unit:</dt>
+ <dd>{p.unit || `{{.}}`}</dd>
+ </Fragment>
+ )}
+ {btr && `{{/unit}}`}
+
+ {btr && `{{#product_id}}`}
+ {(btr || p.product_id) && (
+ <Fragment>
+ <dt>Product ID:</dt>
+ <dd>{p.product_id || `{{.}}`}</dd>
+ </Fragment>
+ )}
+ {btr && `{{/product_id}}`}
+ </dl>
+ </Fragment>
+ );
+ })}
+ {btr && "{{" + "/contract_terms.products" + "}}"}
+ </TableSimple>
+ </section>
+ )}
+ {btr && `{{/contract_terms.hasProducts}}`}
+
+ {btr && `{{#contract_terms.has_delivery_info}}`}
+ {!hasDeliveryInfo ? null : (
+ <section>
+ <h2>Delivery information</h2>
+ <TableExpanded>
+ {btr && `{{#contract_terms.delivery_date}}`}
+ {(btr || contract_terms?.delivery_date) && (
+ <Fragment>
+ <dt>Delivery date:</dt>
+ <dd>
+ {contract_terms?.delivery_date
+ ? contract_terms?.delivery_date.t_s != "never"
+ ? format(
+ contract_terms?.delivery_date.t_s,
+ "dd MMM yyyy HH:mm:ss"
+ )
+ : "never"
+ : `{{ contract_terms.delivery_date_str }}`}{" "}
+ </dd>
+ </Fragment>
+ )}
+ {btr && `{{/contract_terms.delivery_date}}`}
+
+ {btr && `{{#contract_terms.delivery_location}}`}
+ {(btr || contract_terms?.delivery_location) && (
+ <Fragment>
+ <dt>Delivery address:</dt>
+ <Location
+ btr={btr}
+ location={contract_terms?.delivery_location}
+ templateName="contract_terms.delivery_location"
+ />
+ </Fragment>
+ )}
+ {btr && `{{/contract_terms.delivery_location}}`}
+ </TableExpanded>
+ </section>
+ )}
+ {btr && `{{/contract_terms.has_delivery_info}}`}
+
+ <section>
+ <h2>Full payment information</h2>
+ <TableExpanded>
+ <dt>Amount paid:</dt>
+ <dd>{contract_terms?.amount || `{{ contract_terms.amount }}`}</dd>
+ <dt>Wire transfer method:</dt>
+ <dd>
+ {contract_terms?.wire_method ||
+ `{{ contract_terms.wire_method }}`}
+ </dd>
+ <dt>Payment deadline:</dt>
+ <dd>
+ {contract_terms?.pay_deadline
+ ? contract_terms?.pay_deadline.t_s != "never"
+ ? format(
+ contract_terms?.pay_deadline.t_s,
+ "dd MMM yyyy HH:mm:ss"
+ )
+ : "never"
+ : `{{ contract_terms.pay_deadline_str }}`}{" "}
+ </dd>
+ <dt>Exchange transfer deadline:</dt>
+ <dd>
+ {contract_terms?.wire_transfer_deadline
+ ? contract_terms?.wire_transfer_deadline.t_s != "never"
+ ? format(
+ contract_terms?.wire_transfer_deadline.t_s,
+ "dd MMM yyyy HH:mm:ss"
+ )
+ : "never"
+ : `{{ contract_terms.wire_transfer_deadline_str }}`}{" "}
+ </dd>
+ <dt>Maximum deposit fee:</dt>
+ <dd>{contract_terms?.max_fee || `{{ contract_terms.max_fee
}}`}</dd>
+ <dt>Maximum wire fee:</dt>
+ <dd>
+ {contract_terms?.max_wire_fee ||
+ `{{ contract_terms.max_wire_fee }}`}
+ </dd>
+ <dt>Wire fee amortization:</dt>
+ <dd>
+ {contract_terms?.wire_fee_amortization ||
+ `{{ contract_terms.wire_fee_amortization }}`}{" "}
+ transactions
+ </dd>
+ </TableExpanded>
+ </section>
+
+ <section>
+ <h2>Refund information</h2>
+ <TableExpanded>
+ <dt>Refund deadline:</dt>
+ <dd>
+ {contract_terms?.refund_deadline
+ ? contract_terms?.refund_deadline.t_s != "never"
+ ? format(
+ contract_terms?.refund_deadline.t_s,
+ "dd MMM yyyy HH:mm:ss"
+ )
+ : "never"
+ : `{{ contract_terms.refund_deadline_str }}`}{" "}
+ </dd>
+
+ {btr && `{{#contract_terms.auto_refund}}`}
+ {(btr || contract_terms?.auto_refund) && (
+ <Fragment>
+ <dt>Attempt autorefund for:</dt>
+ <dd>
+ {contract_terms?.auto_refund
+ ? contract_terms?.auto_refund.d_us != "forever"
+ ? formatDuration(
+ intervalToDuration({
+ start: 0,
+ end: contract_terms?.auto_refund.d_us,
+ })
+ )
+ : "forever"
+ : `{{ contract_terms.auto_refund_str }}`}{" "}
+ </dd>
+ </Fragment>
+ )}
+ {btr && `{{/contract_terms.auto_refund}}`}
+ </TableExpanded>
+ </section>
+
+ <section>
+ <h2>Additional order details</h2>
+ <TableExpanded>
+ <dt>Public reorder URL:</dt>
+ <dd> -- not defined yet -- </dd>
+ {btr && `{{#contract_terms.fulfillment_url}}`}
+ {(btr || contract_terms?.fulfillment_url) && (
+ <Fragment>
+ <dt>Fulfillment URL:</dt>
+ <dd>
+ {contract_terms?.fulfillment_url ||
+ (btr && `{{ contract_terms.fulfillment_url }}`)}
+ </dd>
+ </Fragment>
+ )}
+ {btr && `{{/contract_terms.fulfillment_url}}`}
+ {/* <dt>Fulfillment message:</dt>
<dd> -- not defined yet -- </dd> */}
- </TableExpanded>
- </section>
-
- <section>
- <h2>Full merchant information</h2>
- <TableExpanded>
- <dt>Merchant name:</dt>
- <dd>{contract_terms?.merchant.name || `{{
contract_terms.merchant.name }}`}</dd>
- <dt>Merchant address:</dt>
- <Location btr={btr} location={contract_terms?.merchant.address}
templateName="contract_terms.merchant.address" />
- <dt>Merchant's jurisdiction:</dt>
- <Location btr={btr} location={contract_terms?.merchant.jurisdiction}
templateName="contract_terms.merchant.jurisdiction" />
- <dt>Merchant URI:</dt>
- <dd>{contract_terms?.merchant_base_url || `{{
contract_terms.merchant_base_url }}`}</dd>
- <dt>Merchant's public key:</dt>
- <dd>{contract_terms?.merchant_pub || `{{ contract_terms.merchant_pub
}}`}</dd>
- {/* <dt>Merchant's hash:</dt>
+ </TableExpanded>
+ </section>
+
+ <section>
+ <h2>Full merchant information</h2>
+ <TableExpanded>
+ <dt>Merchant name:</dt>
+ <dd>
+ {contract_terms?.merchant.name ||
+ `{{ contract_terms.merchant.name }}`}
+ </dd>
+ <dt>Merchant address:</dt>
+ <Location
+ btr={btr}
+ location={contract_terms?.merchant.address}
+ templateName="contract_terms.merchant.address"
+ />
+ <dt>Merchant's jurisdiction:</dt>
+ <Location
+ btr={btr}
+ location={contract_terms?.merchant.jurisdiction}
+ templateName="contract_terms.merchant.jurisdiction"
+ />
+ <dt>Merchant URI:</dt>
+ <dd>
+ {contract_terms?.merchant_base_url ||
+ `{{ contract_terms.merchant_base_url }}`}
+ </dd>
+ <dt>Merchant's public key:</dt>
+ <dd>
+ {contract_terms?.merchant_pub ||
+ `{{ contract_terms.merchant_pub }}`}
+ </dd>
+ {/* <dt>Merchant's hash:</dt>
<dd> -- not defined yet -- </dd> */}
- </TableExpanded>
+ </TableExpanded>
+ </section>
+
+ {btr && `{{#contract_terms.hasAuditors}}`}
+ {!auditorsList.length ? null : (
+ <section>
+ <h2>Auditors accepted by the merchant</h2>
+ <TableExpanded>
+ {btr && "{{" + "#contract_terms.auditors" + "}}"}
+ {auditorsList.map((p, i) => {
+ return (
+ <Fragment key={i}>
+ <p>{p.name || `{{name}}`}</p>
+ <dt>Auditor's public key:</dt>
+ <dd>{p.auditor_pub || `{{auditor_pub}}`}</dd>
+ <dt>Auditor's URL:</dt>
+ <dd>{p.url || `{{url}}`}</dd>
+ </Fragment>
+ );
+ })}
+ {btr && "{{" + "/contract_terms.auditors" + "}}"}
+ </TableExpanded>
+ </section>
+ )}
+ {btr && `{{/contract_terms.hasAuditors}}`}
+
+ {btr && `{{#contract_terms.hasExchanges}}`}
+ {!exchangesList.length ? null : (
+ <section>
+ <h2>Exchanges accepted by the merchant</h2>
+ <TableExpanded>
+ {btr && "{{" + "#contract_terms.exchanges" + "}}"}
+ {exchangesList.map((p, i) => {
+ return (
+ <Fragment key={i}>
+ <dt>Exchange's URL:</dt>
+ <dd>{p.url || `{{url}}`}</dd>
+ <dt>Public key:</dt>
+ <dd>{p.master_pub || `{{master_pub}}`}</dd>
+ </Fragment>
+ );
+ })}
+ {btr && "{{" + "/contract_terms.exchanges" + "}}"}
+ </TableExpanded>
+ </section>
+ )}
+ {btr && `{{/contract_terms.hasExchanges}}`}
</section>
- {btr && `{{#contract_terms.hasAuditors}}`}
- {!auditorsList.length ? null : <section>
- <h2>Auditors accepted by the merchant</h2>
- <TableExpanded>
- {btr && '{{' + '#contract_terms.auditors' + '}}'}
- {auditorsList.map((p, i) => {
- return <Fragment key={i}>
- <p>{p.name || `{{name}}`}</p>
- <dt>Auditor's public key:</dt>
- <dd>{p.auditor_pub || `{{auditor_pub}}`}</dd>
- <dt>Auditor's URL:</dt>
- <dd>{p.url || `{{url}}`}</dd>
- </Fragment>
- })}
- {btr && '{{' + '/contract_terms.auditors' + '}}'}
- </TableExpanded>
- </section>}
- {btr && `{{/contract_terms.hasAuditors}}`}
-
- {btr && `{{#contract_terms.hasExchanges}}`}
- {!exchangesList.length ? null : <section>
- <h2>Exchanges accepted by the merchant</h2>
- <TableExpanded>
- {btr && '{{' + '#contract_terms.exchanges' + '}}'}
- {exchangesList.map((p, i) => {
- return <Fragment key={i}>
- <dt>Exchange's URL:</dt>
- <dd>{p.url || `{{url}}`}</dd>
- <dt>Public key:</dt>
- <dd>{p.master_pub || `{{master_pub}}`}</dd>
- </Fragment>
- })}
- {btr && '{{' + '/contract_terms.exchanges' + '}}'}
- </TableExpanded>
- </section>}
- {btr && `{{/contract_terms.hasExchanges}}`}
- </section>
-
- <Footer />
- </Page>
-
+ <Footer />
+ </Page>
+ );
}
export function mount(): void {
try {
- const fromLocation = new URL(window.location.href).searchParams
- const os = fromLocation.get('order_summary') || undefined;
+ const fromLocation = new URL(window.location.href).searchParams;
+ const os = fromLocation.get("order_summary") || undefined;
if (os) {
render(<Head order_summary={os} />, document.head);
}
- const ra = fromLocation.get('refund_amount') || undefined;
- const ct = fromLocation.get('contract_terms') || undefined;
+ const ra = fromLocation.get("refund_amount") || undefined;
+ const ct = fromLocation.get("contract_terms") || undefined;
let contractTerms: MerchantBackend.ContractTerms | undefined;
try {
- contractTerms = JSON.parse((window as any).contractTermsStr)
- } catch { }
-
- render(<ShowOrderDetails
- contract_terms={contractTerms}
- order_summary={os} refund_amount={ra}
- />, document.body);
-
+ contractTerms = JSON.parse((window as any).contractTermsStr);
+ } catch {}
+
+ render(
+ <ShowOrderDetails
+ contract_terms={contractTerms}
+ order_summary={os}
+ refund_amount={ra}
+ />,
+ document.body
+ );
} catch (e) {
console.error("got error", e);
if (e instanceof Error) {
@@ -386,9 +543,9 @@ export function mount(): void {
}
}
-export function buildTimeRendering(): { head: string, body: string } {
+export function buildTimeRendering(): { head: string; body: string } {
return {
head: renderToString(<Head />),
- body: renderToString(<ShowOrderDetails btr />)
- }
+ body: renderToString(<ShowOrderDetails btr />),
+ };
}
diff --git a/packages/merchant-backend/src/utils/amount.ts
b/packages/merchant-backend/src/utils/amount.ts
index 062ddaf..85f2304 100644
--- a/packages/merchant-backend/src/utils/amount.ts
+++ b/packages/merchant-backend/src/utils/amount.ts
@@ -38,10 +38,10 @@ export function mergeRefunds(prev:
MerchantBackend.Orders.RefundDetails[], cur:
let tail;
if (prev.length === 0 || //empty list
- cur.timestamp.t_ms === 'never' || //current doesnt have timestamp
- (tail = prev[prev.length - 1]).timestamp.t_ms === 'never' || // last
doesnt have timestamp
+ cur.timestamp.t_s === 'never' || //current doesnt have timestamp
+ (tail = prev[prev.length - 1]).timestamp.t_s === 'never' || // last doesnt
have timestamp
cur.reason !== tail.reason || //different reason
- Math.abs(cur.timestamp.t_ms - tail.timestamp.t_ms) > 1000 * 60) {//more
than 1 minute difference
+ Math.abs(cur.timestamp.t_s - tail.timestamp.t_s) > 1000 * 60) {//more than
1 minute difference
prev.push(cur)
return prev
diff --git a/packages/merchant-backoffice/src/components/form/InputDate.tsx
b/packages/merchant-backoffice/src/components/form/InputDate.tsx
index c4517c2..cbc68a3 100644
--- a/packages/merchant-backoffice/src/components/form/InputDate.tsx
+++ b/packages/merchant-backoffice/src/components/form/InputDate.tsx
@@ -52,13 +52,13 @@ export function InputDate<T>({
strValue = withTimestampSupport ? "unknown" : "";
} else if (value instanceof Date) {
strValue = format(value, "yyyy/MM/dd");
- } else if (value.t_ms) {
+ } else if (value.t_s) {
strValue =
- value.t_ms === "never"
+ value.t_s === "never"
? withTimestampSupport
? "never"
: ""
- : format(new Date(value.t_ms), "yyyy/MM/dd");
+ : format(new Date(value.t_s), "yyyy/MM/dd");
}
return (
@@ -136,7 +136,7 @@ export function InputDate<T>({
<span data-tooltip={i18n`change value to never`}>
<button
class="button is-info"
- onClick={() => onChange({ t_ms: "never" } as any)}
+ onClick={() => onChange({ t_s: "never" } as any)}
>
<Translate>never</Translate>
</button>
@@ -148,7 +148,7 @@ export function InputDate<T>({
closeFunction={() => setOpened(false)}
dateReceiver={(d) => {
if (withTimestampSupport) {
- onChange({ t_ms: d.getTime() } as any);
+ onChange({ t_s: d.getTime() } as any);
} else {
onChange(d as any);
}
diff --git a/packages/merchant-backoffice/src/components/form/InputDuration.tsx
b/packages/merchant-backoffice/src/components/form/InputDuration.tsx
index e5849b4..6fe5976 100644
--- a/packages/merchant-backoffice/src/components/form/InputDuration.tsx
+++ b/packages/merchant-backoffice/src/components/form/InputDuration.tsx
@@ -15,9 +15,9 @@
*/
/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
import { intervalToDuration, formatDuration } from "date-fns";
import { h, VNode } from "preact";
import { useState } from "preact/hooks";
@@ -32,85 +32,141 @@ export interface Props<T> extends InputProps<T> {
withForever?: boolean;
}
-export function InputDuration<T>({ name, expand, placeholder, tooltip, label,
help, readonly, withForever }: Props<keyof T>): VNode {
- const [opened, setOpened] = useState(false)
- const i18n = useTranslator()
+export function InputDuration<T>({
+ name,
+ expand,
+ placeholder,
+ tooltip,
+ label,
+ help,
+ readonly,
+ withForever,
+}: Props<keyof T>): VNode {
+ const [opened, setOpened] = useState(false);
+ const i18n = useTranslator();
const { error, required, value, onChange } = useField<T>(name);
- let strValue = ''
+ let strValue = "";
if (!value) {
- strValue = ''
- } else if (value.d_ms === 'forever') {
- strValue = i18n`forever`
+ strValue = "";
+ } else if (value.d_us === "forever") {
+ strValue = i18n`forever`;
} else {
- strValue = formatDuration(intervalToDuration({ start: 0, end: value.d_ms
}), {
- locale: {
- formatDistance: (name, value) => {
- switch(name) {
- case 'xMonths': return i18n`${value}M`;
- case 'xYears': return i18n`${value}Y`;
- case 'xDays': return i18n`${value}d`;
- case 'xHours': return i18n`${value}h`;
- case 'xMinutes': return i18n`${value}min`;
- case 'xSeconds': return i18n`${value}sec`;
- }
+ strValue = formatDuration(
+ intervalToDuration({ start: 0, end: value.d_us }),
+ {
+ locale: {
+ formatDistance: (name, value) => {
+ switch (name) {
+ case "xMonths":
+ return i18n`${value}M`;
+ case "xYears":
+ return i18n`${value}Y`;
+ case "xDays":
+ return i18n`${value}d`;
+ case "xHours":
+ return i18n`${value}h`;
+ case "xMinutes":
+ return i18n`${value}min`;
+ case "xSeconds":
+ return i18n`${value}sec`;
+ }
+ },
+ localize: {
+ day: () => "s",
+ month: () => "m",
+ ordinalNumber: () => "th",
+ dayPeriod: () => "p",
+ quarter: () => "w",
+ era: () => "e",
+ },
},
- localize: {
- day: () => 's',
- month: () => 'm',
- ordinalNumber: () => 'th',
- dayPeriod: () => 'p',
- quarter: () => 'w',
- era: () => 'e'
- }
- },
- })
+ }
+ );
}
- 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">
- <div class="field has-addons">
- <p class={expand ? "control is-expanded " : "control "}>
- <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)
}}>
- <a class="button is-static" >
- <span class="icon"><i class="mdi mdi-clock" /></span>
- </a>
+ 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">
+ <div class="field has-addons">
+ <p class={expand ? "control is-expanded " : "control "}>
+ <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);
+ }}
+ >
+ <a class="button is-static">
+ <span class="icon">
+ <i class="mdi mdi-clock" />
+ </span>
+ </a>
+ </div>
</div>
+ {error && <p class="help is-danger">{error}</p>}
</div>
- {error && <p class="help is-danger">{error}</p>}
+ {withForever && (
+ <span data-tooltip={i18n`change value to never`}>
+ <button
+ class="button is-info mr-3"
+ onClick={() => onChange({ d_us: "forever" } as any)}
+ >
+ <Translate>forever</Translate>
+ </button>
+ </span>
+ )}
+ {!readonly && (
+ <span data-tooltip={i18n`change value to empty`}>
+ <button
+ class="button is-info "
+ onClick={() => onChange(undefined as any)}
+ >
+ <Translate>clear</Translate>
+ </button>
+ </span>
+ )}
</div>
- {withForever && <span data-tooltip={i18n`change value to never`}>
- <button class="button is-info mr-3" onClick={() => onChange({ d_ms:
'forever' } as any)}><Translate>forever</Translate></button>
- </span>}
- {!readonly && <span data-tooltip={i18n`change value to empty`}>
- <button class="button is-info " onClick={() => onChange(undefined as
any)} ><Translate>clear</Translate></button>
- </span>}
+ {opened && (
+ <SimpleModal onCancel={() => setOpened(false)}>
+ <DurationPicker
+ days
+ hours
+ minutes
+ value={!value || value.d_us === "forever" ? 0 : value.d_us}
+ onChange={(v) => {
+ onChange({ d_us: v } as any);
+ }}
+ />
+ </SimpleModal>
+ )}
</div>
- {opened && <SimpleModal onCancel={() => setOpened(false)}>
- <DurationPicker days hours minutes
- value={!value || value.d_ms === 'forever' ? 0 : value.d_ms}
- onChange={(v) => { onChange({ d_ms: v } as any) }}
- />
- </SimpleModal>}
- </div>
+ );
}
diff --git
a/packages/merchant-backoffice/src/components/form/InputStock.stories.tsx
b/packages/merchant-backoffice/src/components/form/InputStock.stories.tsx
index 6e2275b..2c01ff4 100644
--- a/packages/merchant-backoffice/src/components/form/InputStock.stories.tsx
+++ b/packages/merchant-backoffice/src/components/form/InputStock.stories.tsx
@@ -15,30 +15,39 @@
*/
/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
-import { addDays } from 'date-fns';
-import { h, VNode } from 'preact';
-import { useState } from 'preact/hooks';
+import { addDays } from "date-fns";
+import { h, VNode } from "preact";
+import { useState } from "preact/hooks";
import { FormProvider } from "./FormProvider";
-import { InputStock, Stock } from './InputStock'
+import { InputStock, Stock } from "./InputStock";
export default {
- title: 'Components/Form/InputStock',
+ title: "Components/Form/InputStock",
component: InputStock,
};
-type T = { stock?: Stock }
+type T = { stock?: Stock };
export const CreateStockEmpty = () => {
- const [state, setState] = useState<Partial<T>>({})
- return <FormProvider<T> name="product" object={state} errors={{}}
valueHandler={setState}>
- <InputStock<T> name="stock" label="Stock" />
- <div><pre>{JSON.stringify(state, undefined, 2)}</pre></div>
- </FormProvider>
-}
+ const [state, setState] = useState<Partial<T>>({});
+ return (
+ <FormProvider<T>
+ name="product"
+ object={state}
+ errors={{}}
+ valueHandler={setState}
+ >
+ <InputStock<T> name="stock" label="Stock" />
+ <div>
+ <pre>{JSON.stringify(state, undefined, 2)}</pre>
+ </div>
+ </FormProvider>
+ );
+};
export const CreateStockUnknownRestock = () => {
const [state, setState] = useState<Partial<T>>({
@@ -46,13 +55,22 @@ export const CreateStockUnknownRestock = () => {
current: 10,
lost: 0,
sold: 0,
- }
- })
- return <FormProvider<T> name="product" object={state} errors={{}}
valueHandler={setState}>
- <InputStock<T> name="stock" label="Stock" />
- <div><pre>{JSON.stringify(state, undefined, 2)}</pre></div>
- </FormProvider>
-}
+ },
+ });
+ return (
+ <FormProvider<T>
+ name="product"
+ object={state}
+ errors={{}}
+ valueHandler={setState}
+ >
+ <InputStock<T> name="stock" label="Stock" />
+ <div>
+ <pre>{JSON.stringify(state, undefined, 2)}</pre>
+ </div>
+ </FormProvider>
+ );
+};
export const CreateStockNoRestock = () => {
const [state, setState] = useState<Partial<T>>({
@@ -60,14 +78,23 @@ export const CreateStockNoRestock = () => {
current: 10,
lost: 0,
sold: 0,
- nextRestock: { t_ms: 'never' }
- }
- })
- return <FormProvider<T> name="product" object={state} errors={{}}
valueHandler={setState}>
- <InputStock<T> name="stock" label="Stock" />
- <div><pre>{JSON.stringify(state, undefined, 2)}</pre></div>
- </FormProvider>
-}
+ nextRestock: { t_s: "never" },
+ },
+ });
+ return (
+ <FormProvider<T>
+ name="product"
+ object={state}
+ errors={{}}
+ valueHandler={setState}
+ >
+ <InputStock<T> name="stock" label="Stock" />
+ <div>
+ <pre>{JSON.stringify(state, undefined, 2)}</pre>
+ </div>
+ </FormProvider>
+ );
+};
export const CreateStockWithRestock = () => {
const [state, setState] = useState<Partial<T>>({
@@ -75,14 +102,23 @@ export const CreateStockWithRestock = () => {
current: 15,
lost: 0,
sold: 0,
- nextRestock: { t_ms: addDays(new Date(), 1).getTime() }
- }
- })
- return <FormProvider<T> name="product" object={state} errors={{}}
valueHandler={setState}>
- <InputStock<T> name="stock" label="Stock" />
- <div><pre>{JSON.stringify(state, undefined, 2)}</pre></div>
- </FormProvider>
-}
+ nextRestock: { t_s: addDays(new Date(), 1).getTime() },
+ },
+ });
+ return (
+ <FormProvider<T>
+ name="product"
+ object={state}
+ errors={{}}
+ valueHandler={setState}
+ >
+ <InputStock<T> name="stock" label="Stock" />
+ <div>
+ <pre>{JSON.stringify(state, undefined, 2)}</pre>
+ </div>
+ </FormProvider>
+ );
+};
export const UpdatingProductWithManagedStock = () => {
const [state, setState] = useState<Partial<T>>({
@@ -90,21 +126,37 @@ export const UpdatingProductWithManagedStock = () => {
current: 100,
lost: 0,
sold: 0,
- nextRestock: { t_ms: addDays(new Date(), 1).getTime() }
- }
- })
- return <FormProvider<T> name="product" object={state} errors={{}}
valueHandler={setState}>
- <InputStock<T> name="stock" label="Stock" alreadyExist />
- <div><pre>{JSON.stringify(state, undefined, 2)}</pre></div>
- </FormProvider>
-}
+ nextRestock: { t_s: addDays(new Date(), 1).getTime() },
+ },
+ });
+ return (
+ <FormProvider<T>
+ name="product"
+ object={state}
+ errors={{}}
+ valueHandler={setState}
+ >
+ <InputStock<T> name="stock" label="Stock" alreadyExist />
+ <div>
+ <pre>{JSON.stringify(state, undefined, 2)}</pre>
+ </div>
+ </FormProvider>
+ );
+};
export const UpdatingProductWithInfiniteStock = () => {
- const [state, setState] = useState<Partial<T>>({})
- return <FormProvider<T> name="product" object={state} errors={{}}
valueHandler={setState}>
- <InputStock<T> name="stock" label="Stock" alreadyExist />
- <div><pre>{JSON.stringify(state, undefined, 2)}</pre></div>
- </FormProvider>
-}
-
-
+ const [state, setState] = useState<Partial<T>>({});
+ return (
+ <FormProvider<T>
+ name="product"
+ object={state}
+ errors={{}}
+ valueHandler={setState}
+ >
+ <InputStock<T> name="stock" label="Stock" alreadyExist />
+ <div>
+ <pre>{JSON.stringify(state, undefined, 2)}</pre>
+ </div>
+ </FormProvider>
+ );
+};
diff --git
a/packages/merchant-backoffice/src/components/product/ProductForm.tsx
b/packages/merchant-backoffice/src/components/product/ProductForm.tsx
index 45e70c2..831bb05 100644
--- a/packages/merchant-backoffice/src/components/product/ProductForm.tsx
+++ b/packages/merchant-backoffice/src/components/product/ProductForm.tsx
@@ -15,19 +15,20 @@
*/
/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
import { h } from "preact";
import { useCallback, useEffect, useState } from "preact/hooks";
-import * as yup from 'yup';
+import * as yup from "yup";
import { useBackendContext } from "../../context/backend";
import { MerchantBackend } from "../../declaration";
import { useTranslator } from "../../i18n";
import {
- ProductCreateSchema as createSchema, ProductUpdateSchema as updateSchema
-} from '../../schemas';
+ ProductCreateSchema as createSchema,
+ ProductUpdateSchema as updateSchema,
+} from "../../schemas";
import { FormProvider, FormErrors } from "../form/FormProvider";
import { Input } from "../form/Input";
import { InputCurrency } from "../form/InputCurrency";
@@ -36,7 +37,7 @@ import { InputStock, Stock } from "../form/InputStock";
import { InputTaxes } from "../form/InputTaxes";
import { InputWithAddon } from "../form/InputWithAddon";
-type Entity = MerchantBackend.Products.ProductDetail & { product_id: string }
+type Entity = MerchantBackend.Products.ProductDetail & { product_id: string };
interface Props {
onSubscribe: (c?: () => Entity | undefined) => void;
@@ -44,67 +45,122 @@ interface Props {
alreadyExist?: boolean;
}
-export function ProductForm({ onSubscribe, initial, alreadyExist, }: Props) {
+export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) {
const [value, valueHandler] = useState<Partial<Entity & { stock: Stock }>>({
address: {},
description_i18n: {},
taxes: [],
- next_restock: { t_ms: 'never' },
+ next_restock: { t_s: "never" },
...initial,
- price: ':0',
- stock: !initial || initial.total_stock === -1 ? undefined : {
- current: initial.total_stock || 0,
- lost: initial.total_lost || 0,
- sold: initial.total_sold || 0,
- address: initial.address,
- nextRestock: initial.next_restock,
- }
- })
- let errors : FormErrors<Entity>= {}
+ price: ":0",
+ stock:
+ !initial || initial.total_stock === -1
+ ? undefined
+ : {
+ current: initial.total_stock || 0,
+ lost: initial.total_lost || 0,
+ sold: initial.total_sold || 0,
+ address: initial.address,
+ nextRestock: initial.next_restock,
+ },
+ });
+ let errors: FormErrors<Entity> = {};
try {
- (alreadyExist ? updateSchema : createSchema).validateSync(value, {
abortEarly: false })
+ (alreadyExist ? updateSchema : createSchema).validateSync(value, {
+ abortEarly: false,
+ });
} catch (err) {
if (err instanceof yup.ValidationError) {
- const yupErrors = err.inner as yup.ValidationError[]
- errors = yupErrors.reduce((prev, cur) => !cur.path ? prev : ({ ...prev,
[cur.path]: cur.message }), {})
+ const yupErrors = err.inner as yup.ValidationError[];
+ errors = yupErrors.reduce(
+ (prev, cur) =>
+ !cur.path ? prev : { ...prev, [cur.path]: cur.message },
+ {}
+ );
}
}
- const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !==
undefined)
+ const hasErrors = Object.keys(errors).some(
+ (k) => (errors as any)[k] !== undefined
+ );
const submit = useCallback((): Entity | undefined => {
const stock: Stock = (value as any).stock;
if (!stock) {
- value.total_stock = -1
+ value.total_stock = -1;
} else {
value.total_stock = stock.current;
value.total_lost = stock.lost;
- value.next_restock = stock.nextRestock instanceof Date ? { t_ms:
stock.nextRestock.getTime() } : stock.nextRestock;
+ value.next_restock =
+ stock.nextRestock instanceof Date
+ ? { t_s: stock.nextRestock.getTime() }
+ : stock.nextRestock;
value.address = stock.address;
}
delete (value as any).stock;
- return value as MerchantBackend.Products.ProductDetail & { product_id:
string }
- }, [value])
+ return value as MerchantBackend.Products.ProductDetail & {
+ product_id: string;
+ };
+ }, [value]);
useEffect(() => {
- onSubscribe(hasErrors ? undefined : submit)
- }, [submit, hasErrors])
+ onSubscribe(hasErrors ? undefined : submit);
+ }, [submit, hasErrors]);
const backend = useBackendContext();
- const i18n = useTranslator()
-
- return <div>
- <FormProvider<Entity> name="product" errors={errors} object={value}
valueHandler={valueHandler} >
-
- {alreadyExist ? undefined : <InputWithAddon<Entity> name="product_id"
addonBefore={`${backend.url}/product/`} label={i18n`ID`} tooltip={i18n`product
identification to use in URLs (for internal use only)`} />}
- <InputImage<Entity> name="image" label={i18n`Image`}
tooltip={i18n`illustration of the product for customers`} />
- <Input<Entity> name="description" inputType="multiline"
label={i18n`Description`} tooltip={i18n`product description for customers`} />
- <Input<Entity> name="unit" label={i18n`Unit`} tooltip={i18n`unit
describing quantity of product sold (e.g. 2 kilograms, 5 liters, 3 items, 5
meters) for customers`} />
- <InputCurrency<Entity> name="price" label={i18n`Price`}
tooltip={i18n`sale price for customers, including taxes, for above units of the
product`} />
- <InputStock name="stock" label={i18n`Stock`} alreadyExist={alreadyExist}
tooltip={i18n`product inventory for products with finite supply (for internal
use only)`} />
- <InputTaxes<Entity> name="taxes" label={i18n`Taxes`} tooltip={i18n`taxes
included in the product price, exposed to customers`} />
- </FormProvider>
- </div>
+ const i18n = useTranslator();
+
+ return (
+ <div>
+ <FormProvider<Entity>
+ name="product"
+ errors={errors}
+ object={value}
+ valueHandler={valueHandler}
+ >
+ {alreadyExist ? undefined : (
+ <InputWithAddon<Entity>
+ name="product_id"
+ addonBefore={`${backend.url}/product/`}
+ label={i18n`ID`}
+ tooltip={i18n`product identification to use in URLs (for internal
use only)`}
+ />
+ )}
+ <InputImage<Entity>
+ name="image"
+ label={i18n`Image`}
+ tooltip={i18n`illustration of the product for customers`}
+ />
+ <Input<Entity>
+ name="description"
+ inputType="multiline"
+ label={i18n`Description`}
+ tooltip={i18n`product description for customers`}
+ />
+ <Input<Entity>
+ name="unit"
+ label={i18n`Unit`}
+ tooltip={i18n`unit describing quantity of product sold (e.g. 2
kilograms, 5 liters, 3 items, 5 meters) for customers`}
+ />
+ <InputCurrency<Entity>
+ name="price"
+ label={i18n`Price`}
+ tooltip={i18n`sale price for customers, including taxes, for above
units of the product`}
+ />
+ <InputStock
+ name="stock"
+ label={i18n`Stock`}
+ alreadyExist={alreadyExist}
+ tooltip={i18n`product inventory for products with finite supply (for
internal use only)`}
+ />
+ <InputTaxes<Entity>
+ name="taxes"
+ label={i18n`Taxes`}
+ tooltip={i18n`taxes included in the product price, exposed to
customers`}
+ />
+ </FormProvider>
+ </div>
+ );
}
diff --git a/packages/merchant-backoffice/src/declaration.d.ts
b/packages/merchant-backoffice/src/declaration.d.ts
index 35b80c6..377b030 100644
--- a/packages/merchant-backoffice/src/declaration.d.ts
+++ b/packages/merchant-backoffice/src/declaration.d.ts
@@ -35,12 +35,10 @@ interface Timestamp {
// Milliseconds since epoch, or the special
// value "forever" to represent an event that will
// never happen.
- t_ms: number | "never";
+ t_s: number | "never";
}
interface Duration {
- // Duration in milliseconds or "forever"
- // to represent an infinite duration.
- d_ms: number | "forever";
+ d_us: number | "forever";
}
interface WithId {
@@ -190,7 +188,7 @@ export namespace MerchantBackend {
taxes: Tax[];
// time indicating when this product should be delivered
- delivery_date?: Timestamp;
+ delivery_date?: TalerProtocolTimestamp;
}
interface Merchant {
// label for a location with the business address of the merchant
@@ -1347,17 +1345,17 @@ export namespace MerchantBackend {
products: Product[];
// Time when this contract was generated
- timestamp: Timestamp;
+ timestamp: TalerProtocolTimestamp;
// After this deadline has passed, no refunds will be accepted.
- refund_deadline: Timestamp;
+ refund_deadline: TalerProtocolTimestamp;
// After this deadline, the merchant won't accept payments for the
contact
- pay_deadline: Timestamp;
+ pay_deadline: TalerProtocolTimestamp;
// Transfer deadline for the exchange. Must be in the
// deposit permissions of coins used to pay for this order.
- wire_transfer_deadline: Timestamp;
+ wire_transfer_deadline: TalerProtocolTimestamp;
// Merchant's public key used to sign this proposal; this information
// is typically added by the backend Note that this can be an
ephemeral key.
@@ -1390,7 +1388,7 @@ export namespace MerchantBackend {
// Time indicating when the order should be delivered.
// May be overwritten by individual products.
- delivery_date?: Timestamp;
+ delivery_date?: TalerProtocolTimestamp;
// Nonce generated by the wallet and echoed by the merchant
// in this field when the proposal is generated.
diff --git a/packages/merchant-backoffice/src/hooks/order.ts
b/packages/merchant-backoffice/src/hooks/order.ts
index 0f8afce..788cc17 100644
--- a/packages/merchant-backoffice/src/hooks/order.ts
+++ b/packages/merchant-backoffice/src/hooks/order.ts
@@ -291,7 +291,7 @@ export function useInstanceOrders(
} else {
const from =
afterData.data.orders[afterData.data.orders.length - 1].timestamp
- .t_ms;
+ .t_s;
if (from && updateFilter) updateFilter(new Date(from));
}
},
@@ -302,7 +302,7 @@ export function useInstanceOrders(
} else if (beforeData) {
const from =
beforeData.data.orders[beforeData.data.orders.length - 1].timestamp
- .t_ms;
+ .t_s;
if (from && updateFilter) updateFilter(new Date(from));
}
},
diff --git a/packages/merchant-backoffice/src/paths/admin/create/CreatePage.tsx
b/packages/merchant-backoffice/src/paths/admin/create/CreatePage.tsx
index f1214c9..465e18f 100644
--- a/packages/merchant-backoffice/src/paths/admin/create/CreatePage.tsx
+++ b/packages/merchant-backoffice/src/paths/admin/create/CreatePage.tsx
@@ -48,9 +48,9 @@ function with_defaults(id?: string): Partial<Entity> {
return {
id,
payto_uris: [],
- default_pay_delay: { d_ms: 1000 * 60 * 60 }, // one hour
+ default_pay_delay: { d_us: 1000 * 60 * 60 }, // one hour
default_wire_fee_amortization: 1,
- default_wire_transfer_delay: { d_ms: 1000 * 2 * 60 * 60 * 24 }, // one day
+ default_wire_transfer_delay: { d_us: 1000 * 2 * 60 * 60 * 24 }, // one day
};
}
diff --git
a/packages/merchant-backoffice/src/paths/instance/details/DetailPage.tsx
b/packages/merchant-backoffice/src/paths/instance/details/DetailPage.tsx
index cc177f3..2561f58 100644
--- a/packages/merchant-backoffice/src/paths/instance/details/DetailPage.tsx
+++ b/packages/merchant-backoffice/src/paths/instance/details/DetailPage.tsx
@@ -38,8 +38,8 @@ function convert(from:
MerchantBackend.Instances.QueryInstancesResponse): Entity
const payto_uris = accounts.filter(a => a.active).map(a => a.payto_uri)
const defaults = {
default_wire_fee_amortization: 1,
- default_pay_delay: { d_ms: 1000 * 60 * 60 }, //one hour
- default_wire_transfer_delay: { d_ms: 1000 * 60 * 60 * 2 }, //two hours
+ default_pay_delay: { d_us: 1000 * 60 * 60 }, //one hour
+ default_wire_transfer_delay: { d_us: 1000 * 60 * 60 * 2 }, //two hours
}
return { ...defaults, ...rest, payto_uris };
}
diff --git
a/packages/merchant-backoffice/src/paths/instance/details/Details.stories.tsx
b/packages/merchant-backoffice/src/paths/instance/details/Details.stories.tsx
index 21141f7..fb7c914 100644
---
a/packages/merchant-backoffice/src/paths/instance/details/Details.stories.tsx
+++
b/packages/merchant-backoffice/src/paths/instance/details/Details.stories.tsx
@@ -15,46 +15,47 @@
*/
/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-
-import { h, VNode, FunctionalComponent } from 'preact';
-import { DetailPage as TestedComponent } from './DetailPage';
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+import { h, VNode, FunctionalComponent } from "preact";
+import { DetailPage as TestedComponent } from "./DetailPage";
export default {
- title: 'Pages/Instance/Detail',
+ title: "Pages/Instance/Detail",
component: TestedComponent,
argTypes: {
- onUpdate: { action: 'onUpdate' },
- onBack: { action: 'onBack' },
+ onUpdate: { action: "onUpdate" },
+ onBack: { action: "onBack" },
},
};
-function createExample<Props>(Component: FunctionalComponent<Props>, props:
Partial<Props>) {
- const r = (args: any) => <Component {...args} />
- r.args = props
- return r
+function createExample<Props>(
+ Component: FunctionalComponent<Props>,
+ props: Partial<Props>
+) {
+ const r = (args: any) => <Component {...args} />;
+ r.args = props;
+ return r;
}
export const Example = createExample(TestedComponent, {
selected: {
accounts: [],
- name: 'name',
- auth: {method:'external'},
+ name: "name",
+ auth: { method: "external" },
address: {},
jurisdiction: {},
- default_max_deposit_fee: 'TESTKUDOS:2',
- default_max_wire_fee: 'TESTKUDOS:1',
+ default_max_deposit_fee: "TESTKUDOS:2",
+ default_max_wire_fee: "TESTKUDOS:1",
default_pay_delay: {
- d_ms: 1000000,
+ d_us: 1000000,
},
default_wire_fee_amortization: 1,
default_wire_transfer_delay: {
- d_ms: 100000,
+ d_us: 100000,
},
- merchant_pub: 'ASDWQEKASJDKSADJ'
- }
+ merchant_pub: "ASDWQEKASJDKSADJ",
+ },
});
-
diff --git
a/packages/merchant-backoffice/src/paths/instance/orders/create/Create.stories.tsx
b/packages/merchant-backoffice/src/paths/instance/orders/create/Create.stories.tsx
index a3a2987..43df848 100644
---
a/packages/merchant-backoffice/src/paths/instance/orders/create/Create.stories.tsx
+++
b/packages/merchant-backoffice/src/paths/instance/orders/create/Create.stories.tsx
@@ -15,50 +15,56 @@
*/
/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-
-import { h, VNode, FunctionalComponent } from 'preact';
-import { CreatePage as TestedComponent } from './CreatePage';
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+import { h, VNode, FunctionalComponent } from "preact";
+import { CreatePage as TestedComponent } from "./CreatePage";
export default {
- title: 'Pages/Order/Create',
+ title: "Pages/Order/Create",
component: TestedComponent,
argTypes: {
- onCreate: { action: 'onCreate' },
- goBack: { action: 'goBack' },
+ onCreate: { action: "onCreate" },
+ goBack: { action: "goBack" },
},
};
-function createExample<Props>(Component: FunctionalComponent<Props>, props:
Partial<Props>) {
- const r = (args: any) => <Component {...args} />
- r.args = props
- return r
+function createExample<Props>(
+ Component: FunctionalComponent<Props>,
+ props: Partial<Props>
+) {
+ const r = (args: any) => <Component {...args} />;
+ r.args = props;
+ return r;
}
export const Example = createExample(TestedComponent, {
instanceConfig: {
- default_max_deposit_fee: '',
- default_max_wire_fee: '',
+ default_max_deposit_fee: "",
+ default_max_wire_fee: "",
default_pay_delay: {
- d_ms: 1000*60*60
+ d_us: 1000 * 60 * 60,
},
- default_wire_fee_amortization: 1
+ default_wire_fee_amortization: 1,
},
- instanceInventory: [{
- id: 't-shirt-1',
- description: 'a m size t-shirt',
- price: 'TESTKUDOS:1',
- total_stock: -1
- },{
- id: 't-shirt-2',
- price: 'TESTKUDOS:1',
- description: 'a xl size t-shirt'
- } as any,{
- id: 't-shirt-3',
- price: 'TESTKUDOS:1',
- description: 'a s size t-shirt'
- } as any]
+ instanceInventory: [
+ {
+ id: "t-shirt-1",
+ description: "a m size t-shirt",
+ price: "TESTKUDOS:1",
+ total_stock: -1,
+ },
+ {
+ id: "t-shirt-2",
+ price: "TESTKUDOS:1",
+ description: "a xl size t-shirt",
+ } as any,
+ {
+ id: "t-shirt-3",
+ price: "TESTKUDOS:1",
+ description: "a s size t-shirt",
+ } as any,
+ ],
});
diff --git
a/packages/merchant-backoffice/src/paths/instance/orders/create/CreatePage.tsx
b/packages/merchant-backoffice/src/paths/instance/orders/create/CreatePage.tsx
index 580ead1..2d6b725 100644
---
a/packages/merchant-backoffice/src/paths/instance/orders/create/CreatePage.tsx
+++
b/packages/merchant-backoffice/src/paths/instance/orders/create/CreatePage.tsx
@@ -58,9 +58,9 @@ interface InstanceConfig {
function with_defaults(config: InstanceConfig): Partial<Entity> {
const defaultPayDeadline =
- !config.default_pay_delay || config.default_pay_delay.d_ms === "forever"
+ !config.default_pay_delay || config.default_pay_delay.d_us === "forever"
? undefined
- : add(new Date(), { seconds: config.default_pay_delay.d_ms / 1000 });
+ : add(new Date(), { seconds: config.default_pay_delay.d_us / 1000 });
return {
inventoryProducts: {},
@@ -223,13 +223,13 @@ export function CreatePage({
extra: value.extra,
pay_deadline: value.payments.pay_deadline
? {
- t_ms:
+ t_s:
Math.floor(value.payments.pay_deadline.getTime() / 1000) *
1000,
}
: undefined,
wire_transfer_deadline: value.payments.wire_transfer_deadline
? {
- t_ms:
+ t_s:
Math.floor(
value.payments.wire_transfer_deadline.getTime() / 1000
) * 1000,
@@ -237,7 +237,7 @@ export function CreatePage({
: undefined,
refund_deadline: value.payments.refund_deadline
? {
- t_ms:
+ t_s:
Math.floor(value.payments.refund_deadline.getTime() / 1000) *
1000,
}
@@ -247,7 +247,7 @@ export function CreatePage({
max_wire_fee: value.payments.max_wire_fee,
delivery_date: value.shipping.delivery_date
- ? { t_ms: value.shipping.delivery_date.getTime() }
+ ? { t_s: value.shipping.delivery_date.getTime() }
: undefined,
delivery_location: value.shipping.delivery_location,
fulfillment_url: value.shipping.fullfilment_url,
diff --git
a/packages/merchant-backoffice/src/paths/instance/orders/details/Detail.stories.tsx
b/packages/merchant-backoffice/src/paths/instance/orders/details/Detail.stories.tsx
index 4e43a54..53bc4da 100644
---
a/packages/merchant-backoffice/src/paths/instance/orders/details/Detail.stories.tsx
+++
b/packages/merchant-backoffice/src/paths/instance/orders/details/Detail.stories.tsx
@@ -15,123 +15,123 @@
*/
/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-
-import { addDays } from 'date-fns';
-import { h, VNode, FunctionalComponent } from 'preact';
-import { MerchantBackend } from '../../../../declaration';
-import { DetailPage as TestedComponent } from './DetailPage';
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+import { addDays } from "date-fns";
+import { h, VNode, FunctionalComponent } from "preact";
+import { MerchantBackend } from "../../../../declaration";
+import { DetailPage as TestedComponent } from "./DetailPage";
export default {
- title: 'Pages/Order/Detail',
+ title: "Pages/Order/Detail",
component: TestedComponent,
argTypes: {
- onRefund: { action: 'onRefund' },
- onBack: { action: 'onBack' },
+ onRefund: { action: "onRefund" },
+ onBack: { action: "onBack" },
},
};
-function createExample<Props>(Component: FunctionalComponent<Props>, props:
Partial<Props>) {
- const r = (args: any) => <Component {...args} />
- r.args = props
- return r
+function createExample<Props>(
+ Component: FunctionalComponent<Props>,
+ props: Partial<Props>
+) {
+ const r = (args: any) => <Component {...args} />;
+ r.args = props;
+ return r;
}
const defaultContractTerm = {
- amount: 'TESTKUDOS:10',
+ amount: "TESTKUDOS:10",
timestamp: {
- t_ms: new Date().getTime(),
+ t_s: new Date().getTime(),
},
auditors: [],
exchanges: [],
- max_fee: 'TESTKUDOS:1',
- max_wire_fee: 'TESTKUDOS:1',
- merchant: {
-
- } as any,
- merchant_base_url: 'http://merchant.url/',
- order_id: '2021.165-03GDFC26Y1NNG',
+ max_fee: "TESTKUDOS:1",
+ max_wire_fee: "TESTKUDOS:1",
+ merchant: {} as any,
+ merchant_base_url: "http://merchant.url/",
+ order_id: "2021.165-03GDFC26Y1NNG",
products: [],
- summary: 'text summary',
+ summary: "text summary",
wire_fee_amortization: 1,
wire_transfer_deadline: {
- t_ms: 'never',
+ t_s: "never",
},
- refund_deadline: { t_ms: 'never' },
- merchant_pub: 'ASDASDASDSd',
- nonce: 'QWEQWEQWE',
+ refund_deadline: { t_s: "never" },
+ merchant_pub: "ASDASDASDSd",
+ nonce: "QWEQWEQWE",
pay_deadline: {
- t_ms: 'never',
+ t_s: "never",
},
- wire_method: 'x-taler-bank',
- h_wire: 'asd',
-} as MerchantBackend.ContractTerms
+ wire_method: "x-taler-bank",
+ h_wire: "asd",
+} as MerchantBackend.ContractTerms;
// contract_terms: defaultContracTerm,
export const Claimed = createExample(TestedComponent, {
- id: '2021.165-03GDFC26Y1NNG',
+ id: "2021.165-03GDFC26Y1NNG",
selected: {
- order_status: 'claimed',
- contract_terms: defaultContractTerm
+ order_status: "claimed",
+ contract_terms: defaultContractTerm,
},
});
export const PaidNotRefundable = createExample(TestedComponent, {
- id: '2021.165-03GDFC26Y1NNG',
+ id: "2021.165-03GDFC26Y1NNG",
selected: {
- order_status: 'paid',
+ order_status: "paid",
contract_terms: defaultContractTerm,
refunded: false,
- deposit_total: 'TESTKUDOS:10',
+ deposit_total: "TESTKUDOS:10",
exchange_ec: 0,
- order_status_url: 'http://merchant.backend/status',
+ order_status_url: "http://merchant.backend/status",
exchange_hc: 0,
- refund_amount: 'TESTKUDOS:0',
+ refund_amount: "TESTKUDOS:0",
refund_details: [],
refund_pending: false,
wire_details: [],
wire_reports: [],
wired: false,
- }
+ },
});
export const PaidRefundable = createExample(TestedComponent, {
- id: '2021.165-03GDFC26Y1NNG',
+ id: "2021.165-03GDFC26Y1NNG",
selected: {
- order_status: 'paid',
+ order_status: "paid",
contract_terms: {
...defaultContractTerm,
refund_deadline: {
- t_ms: addDays(new Date(), 2).getTime()
- }
+ t_s: addDays(new Date(), 2).getTime(),
+ },
},
refunded: false,
- deposit_total: 'TESTKUDOS:10',
+ deposit_total: "TESTKUDOS:10",
exchange_ec: 0,
- order_status_url: 'http://merchant.backend/status',
+ order_status_url: "http://merchant.backend/status",
exchange_hc: 0,
- refund_amount: 'TESTKUDOS:0',
+ refund_amount: "TESTKUDOS:0",
refund_details: [],
refund_pending: false,
wire_details: [],
wire_reports: [],
wired: false,
- }
+ },
});
export const Unpaid = createExample(TestedComponent, {
- id: '2021.165-03GDFC26Y1NNG',
+ id: "2021.165-03GDFC26Y1NNG",
selected: {
- order_status: 'unpaid',
- order_status_url: 'http://merchant.backend/status',
+ order_status: "unpaid",
+ order_status_url: "http://merchant.backend/status",
creation_time: {
- t_ms: new Date().getTime()
+ t_s: new Date().getTime(),
},
- summary: 'text summary',
- taler_pay_uri: 'pay uri',
- total_amount: 'TESTKUDOS:10',
- }
+ summary: "text summary",
+ taler_pay_uri: "pay uri",
+ total_amount: "TESTKUDOS:10",
+ },
});
diff --git
a/packages/merchant-backoffice/src/paths/instance/orders/details/DetailPage.tsx
b/packages/merchant-backoffice/src/paths/instance/orders/details/DetailPage.tsx
index 9f2bce3..cdc2768 100644
---
a/packages/merchant-backoffice/src/paths/instance/orders/details/DetailPage.tsx
+++
b/packages/merchant-backoffice/src/paths/instance/orders/details/DetailPage.tsx
@@ -15,9 +15,9 @@
*/
/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
import { AmountJson, Amounts } from "@gnu-taler/taler-util";
import { format } from "date-fns";
@@ -39,7 +39,7 @@ import { RefundModal } from "../list/Table";
import { Event, Timeline } from "./Timeline";
type Entity = MerchantBackend.Orders.MerchantOrderStatusResponse;
-type CT = MerchantBackend.ContractTerms
+type CT = MerchantBackend.ContractTerms;
interface Props {
onBack: () => void;
@@ -48,430 +48,687 @@ interface Props {
onRefund: (id: string, value: MerchantBackend.Orders.RefundRequest) => void;
}
-type Paid = MerchantBackend.Orders.CheckPaymentPaidResponse
-type Unpaid = MerchantBackend.Orders.CheckPaymentUnpaidResponse
-type Claimed = MerchantBackend.Orders.CheckPaymentClaimedResponse
-
+type Paid = MerchantBackend.Orders.CheckPaymentPaidResponse;
+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>
+ 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[] = []
- if (order.contract_terms.timestamp.t_ms !== 'never') {
+function ClaimedPage({
+ id,
+ order,
+}: {
+ id: string;
+ order: MerchantBackend.Orders.CheckPaymentClaimedResponse;
+}) {
+ const events: Event[] = [];
+ if (order.contract_terms.timestamp.t_s !== "never") {
events.push({
- when: new Date(order.contract_terms.timestamp.t_ms),
- description: 'order created',
- type: 'start'
- })
+ when: new Date(order.contract_terms.timestamp.t_s),
+ description: "order created",
+ type: "start",
+ });
}
- if (order.contract_terms.pay_deadline.t_ms !== 'never') {
+ if (order.contract_terms.pay_deadline.t_s !== "never") {
events.push({
- when: new Date(order.contract_terms.pay_deadline.t_ms),
- description: 'pay deadline',
- type: 'deadline'
- })
+ when: new Date(order.contract_terms.pay_deadline.t_s),
+ description: "pay deadline",
+ type: "deadline",
+ });
}
- if (order.contract_terms.refund_deadline.t_ms !== 'never') {
+ if (order.contract_terms.refund_deadline.t_s !== "never") {
events.push({
- when: new Date(order.contract_terms.refund_deadline.t_ms),
- description: 'refund deadline',
- type: 'deadline'
- })
+ when: new Date(order.contract_terms.refund_deadline.t_s),
+ description: "refund deadline",
+ type: "deadline",
+ });
}
- if (order.contract_terms.wire_transfer_deadline.t_ms !== 'never') {
+ if (order.contract_terms.wire_transfer_deadline.t_s !== "never") {
events.push({
- when: new Date(order.contract_terms.wire_transfer_deadline.t_ms),
- description: 'wire deadline',
- type: 'deadline'
- })
+ when: new Date(order.contract_terms.wire_transfer_deadline.t_s),
+ description: "wire deadline",
+ type: "deadline",
+ });
}
- if (order.contract_terms.delivery_date &&
order.contract_terms.delivery_date.t_ms !== 'never') {
+ if (
+ order.contract_terms.delivery_date &&
+ order.contract_terms.delivery_date.t_s !== "never"
+ ) {
events.push({
- when: new Date(order.contract_terms.delivery_date?.t_ms),
- description: 'delivery',
- type: 'delivery'
- })
+ when: new Date(order.contract_terms.delivery_date?.t_s),
+ description: "delivery",
+ type: "delivery",
+ });
}
- const [value, valueHandler] = useState<Partial<Claimed>>(order)
- const i18n = useTranslator()
-
- return <div>
- <section class="section">
- <div class="columns">
- <div class="column" />
- <div class="column is-10">
-
- <section class="hero is-hero-bar">
- <div class="hero-body">
-
- <div class="level">
- <div class="level-left">
- <div class="level-item">
- <Translate>Order</Translate> #{id}
- <div class="tag is-info
ml-4"><Translate>claimed</Translate></div>
+ const [value, valueHandler] = useState<Partial<Claimed>>(order);
+ const i18n = useTranslator();
+
+ return (
+ <div>
+ <section class="section">
+ <div class="columns">
+ <div class="column" />
+ <div class="column is-10">
+ <section class="hero is-hero-bar">
+ <div class="hero-body">
+ <div class="level">
+ <div class="level-left">
+ <div class="level-item">
+ <Translate>Order</Translate> #{id}
+ <div class="tag is-info ml-4">
+ <Translate>claimed</Translate>
+ </div>
+ </div>
</div>
</div>
- </div>
- <div class="level">
- <div class="level-left">
- <div class="level-item">
- <h1 class="title">
- {order.contract_terms.amount}
- </h1>
+ <div class="level">
+ <div class="level-left">
+ <div class="level-item">
+ <h1 class="title">{order.contract_terms.amount}</h1>
+ </div>
</div>
</div>
- </div>
- <div class="level">
- <div class="level-left" style={{ maxWidth: '100%' }}>
- <div class="level-item" style={{ maxWidth: '100%' }}>
- <div class="content" style={{
- whiteSpace: 'nowrap',
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- }}>
- <p><b><Translate>claimed at</Translate>:</b> {format(new
Date(order.contract_terms.timestamp.t_ms), 'yyyy-MM-dd HH:mm:ss')}</p>
+ <div class="level">
+ <div class="level-left" style={{ maxWidth: "100%" }}>
+ <div class="level-item" style={{ maxWidth: "100%" }}>
+ <div
+ class="content"
+ style={{
+ whiteSpace: "nowrap",
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ }}
+ >
+ <p>
+ <b>
+ <Translate>claimed at</Translate>:
+ </b>{" "}
+ {format(
+ new Date(order.contract_terms.timestamp.t_s),
+ "yyyy-MM-dd HH:mm:ss"
+ )}
+ </p>
+ </div>
</div>
</div>
</div>
</div>
- </div>
- </section>
+ </section>
- <section class="section">
- <div class="columns">
- <div class="column is-4">
- <div class="title"><Translate>Timeline</Translate></div>
- <Timeline events={events} />
- </div>
- <div class="column is-8" >
- <div class="title"><Translate>Payment details</Translate></div>
- <FormProvider<Claimed> object={value}
valueHandler={valueHandler} >
- <Input name="contract_terms.summary" readonly
inputType="multiline" label={i18n`Summary`} />
- <InputCurrency name="contract_terms.amount" readonly
label={i18n`Amount`} />
- <Input<Claimed> name="order_status" readonly
label={i18n`Order status`} />
- </FormProvider>
+ <section class="section">
+ <div class="columns">
+ <div class="column is-4">
+ <div class="title">
+ <Translate>Timeline</Translate>
+ </div>
+ <Timeline events={events} />
+ </div>
+ <div class="column is-8">
+ <div class="title">
+ <Translate>Payment details</Translate>
+ </div>
+ <FormProvider<Claimed>
+ object={value}
+ valueHandler={valueHandler}
+ >
+ <Input
+ name="contract_terms.summary"
+ readonly
+ inputType="multiline"
+ label={i18n`Summary`}
+ />
+ <InputCurrency
+ name="contract_terms.amount"
+ readonly
+ label={i18n`Amount`}
+ />
+ <Input<Claimed>
+ name="order_status"
+ readonly
+ label={i18n`Order status`}
+ />
+ </FormProvider>
+ </div>
</div>
- </div>
- </section>
+ </section>
- {order.contract_terms.products.length ? <Fragment>
- <div class="title"><Translate>Product list</Translate></div>
- <ProductList list={order.contract_terms.products} />
- </Fragment> : 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}
/>}
+ {value.contract_terms && (
+ <ContractTerms value={value.contract_terms} />
+ )}
+ </div>
+ <div class="column" />
</div>
- <div class="column" />
- </div>
- </section>
- </div>
+ </section>
+ </div>
+ );
}
-function PaidPage({ id, order, onRefund }: { id: string; order:
MerchantBackend.Orders.CheckPaymentPaidResponse, onRefund: (id: string) => void
}) {
- const events: Event[] = []
- if (order.contract_terms.timestamp.t_ms !== 'never') {
+function PaidPage({
+ id,
+ order,
+ onRefund,
+}: {
+ id: string;
+ order: MerchantBackend.Orders.CheckPaymentPaidResponse;
+ onRefund: (id: string) => void;
+}) {
+ const events: Event[] = [];
+ if (order.contract_terms.timestamp.t_s !== "never") {
events.push({
- when: new Date(order.contract_terms.timestamp.t_ms),
- description: 'order created',
- type: 'start'
- })
+ when: new Date(order.contract_terms.timestamp.t_s),
+ description: "order created",
+ type: "start",
+ });
}
- if (order.contract_terms.pay_deadline.t_ms !== 'never') {
+ if (order.contract_terms.pay_deadline.t_s !== "never") {
events.push({
- when: new Date(order.contract_terms.pay_deadline.t_ms),
- description: 'pay deadline',
- type: 'deadline'
- })
-
+ when: new Date(order.contract_terms.pay_deadline.t_s),
+ description: "pay deadline",
+ type: "deadline",
+ });
}
- if (order.contract_terms.refund_deadline.t_ms !== 'never') {
+ if (order.contract_terms.refund_deadline.t_s !== "never") {
events.push({
- when: new Date(order.contract_terms.refund_deadline.t_ms),
- description: 'refund deadline',
- type: 'deadline'
- })
+ when: new Date(order.contract_terms.refund_deadline.t_s),
+ description: "refund deadline",
+ type: "deadline",
+ });
}
- if (order.contract_terms.wire_transfer_deadline.t_ms !== 'never') {
+ if (order.contract_terms.wire_transfer_deadline.t_s !== "never") {
events.push({
- when: new Date(order.contract_terms.wire_transfer_deadline.t_ms),
- description: 'wire deadline',
- type: 'deadline'
- })
+ when: new Date(order.contract_terms.wire_transfer_deadline.t_s),
+ description: "wire deadline",
+ type: "deadline",
+ });
}
- if (order.contract_terms.delivery_date &&
order.contract_terms.delivery_date.t_ms !== 'never') {
- if (order.contract_terms.delivery_date) events.push({
- when: new Date(order.contract_terms.delivery_date?.t_ms),
- description: 'delivery',
- type: 'delivery'
- })
+ if (
+ order.contract_terms.delivery_date &&
+ order.contract_terms.delivery_date.t_s !== "never"
+ ) {
+ if (order.contract_terms.delivery_date)
+ events.push({
+ when: new Date(order.contract_terms.delivery_date?.t_s),
+ description: "delivery",
+ type: "delivery",
+ });
}
- order.refund_details.reduce(mergeRefunds, []).forEach(e => {
+ order.refund_details.reduce(mergeRefunds, []).forEach((e) => {
events.push({
- when: new Date(e.timestamp.t_ms),
+ when: new Date(e.timestamp.t_s),
description: `refund: ${e.amount}: ${e.reason}`,
- type: 'refund',
- })
- })
+ type: "refund",
+ });
+ });
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
+ 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
+ order.wire_details.forEach((w) => {
+ if (last === null || last.execution_time.t_s < w.execution_time.t_s) {
+ last = w;
}
- if (first === null || first.execution_time.t_ms >
w.execution_time.t_ms) {
- first = w
+ if (first === null || first.execution_time.t_s > w.execution_time.t_s)
{
+ first = w;
}
- total = total === null ? Amounts.parseOrThrow(w.amount) :
Amounts.add(total, Amounts.parseOrThrow(w.amount)).amount
- })
+ 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),
+ when: new Date(last!.execution_time.t_s),
description: `wired ${Amounts.stringify(total!)}`,
- type: 'wired-range',
- })
+ type: "wired-range",
+ });
events.push({
- when: new Date(first!.execution_time.t_ms),
+ when: new Date(first!.execution_time.t_s),
description: `wire transfer started...`,
- type: 'wired-range',
- })
+ type: "wired-range",
+ });
} else {
- order.wire_details.forEach(e => {
+ order.wire_details.forEach((e) => {
events.push({
- when: new Date(e.execution_time.t_ms),
+ when: new Date(e.execution_time.t_s),
description: `wired ${e.amount}`,
- type: 'wired',
- })
- })
-
+ type: "wired",
+ });
+ });
}
-
}
- const [value, valueHandler] = useState<Partial<Paid>>(order)
-
- const refundable = new Date().getTime() <
order.contract_terms.refund_deadline.t_ms
- const i18n = useTranslator()
-
- return <div>
- <section class="section">
- <div class="columns">
- <div class="column" />
- <div class="column is-10">
-
- <section class="hero is-hero-bar">
- <div class="hero-body">
-
- <div class="level">
- <div class="level-left">
- <div class="level-item">
- <Translate>Order</Translate> #{id}
- <div class="tag is-success
ml-4"><Translate>paid</Translate></div>
- {order.wired ?
- <div class="tag is-success
ml-4"><Translate>wired</Translate></div> : null
- }
- {order.refunded ?
- <div class="tag is-danger
ml-4"><Translate>refunded</Translate></div> : null
- }
+ const [value, valueHandler] = useState<Partial<Paid>>(order);
+
+ const refundable =
+ new Date().getTime() < order.contract_terms.refund_deadline.t_s;
+ const i18n = useTranslator();
+
+ return (
+ <div>
+ <section class="section">
+ <div class="columns">
+ <div class="column" />
+ <div class="column is-10">
+ <section class="hero is-hero-bar">
+ <div class="hero-body">
+ <div class="level">
+ <div class="level-left">
+ <div class="level-item">
+ <Translate>Order</Translate> #{id}
+ <div class="tag is-success ml-4">
+ <Translate>paid</Translate>
+ </div>
+ {order.wired ? (
+ <div class="tag is-success ml-4">
+ <Translate>wired</Translate>
+ </div>
+ ) : null}
+ {order.refunded ? (
+ <div class="tag is-danger ml-4">
+ <Translate>refunded</Translate>
+ </div>
+ ) : null}
+ </div>
</div>
</div>
- </div>
- <div class="level">
- <div class="level-left">
- <div class="level-item">
- <h1 class="title">
- {order.contract_terms.amount}
- </h1>
+ <div class="level">
+ <div class="level-left">
+ <div class="level-item">
+ <h1 class="title">{order.contract_terms.amount}</h1>
+ </div>
</div>
- </div>
- <div class="level-right">
- <div class="level-item">
- <h1 class="title">
- <div class="buttons">
- <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 class="level-right">
+ <div class="level-item">
+ <h1 class="title">
+ <div class="buttons">
+ <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>
</div>
</div>
- </div>
- <div class="level">
- <div class="level-left" style={{ maxWidth: '100%' }}>
- <div class="level-item" style={{ maxWidth: '100%' }}>
- <div class="content" style={{
- whiteSpace: 'nowrap',
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- // maxWidth: '100%',
- }}>
- <p><a href={order.contract_terms.fulfillment_url}
rel="nofollow" target="new">{order.contract_terms.fulfillment_url}</a></p>
- <p>{format(new
Date(order.contract_terms.timestamp.t_ms), 'yyyy/MM/dd HH:mm:ss')}</p>
+ <div class="level">
+ <div class="level-left" style={{ maxWidth: "100%" }}>
+ <div class="level-item" style={{ maxWidth: "100%" }}>
+ <div
+ class="content"
+ style={{
+ whiteSpace: "nowrap",
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ // maxWidth: '100%',
+ }}
+ >
+ <p>
+ <a
+ href={order.contract_terms.fulfillment_url}
+ rel="nofollow"
+ target="new"
+ >
+ {order.contract_terms.fulfillment_url}
+ </a>
+ </p>
+ <p>
+ {format(
+ new Date(order.contract_terms.timestamp.t_s),
+ "yyyy/MM/dd HH:mm:ss"
+ )}
+ </p>
+ </div>
</div>
</div>
</div>
</div>
- </div>
- </section>
+ </section>
- <section class="section">
- <div class="columns">
- <div class="column is-4">
- <div class="title"><Translate>Timeline</Translate></div>
- <Timeline events={events} />
- </div>
- <div class="column is-8" >
- <div class="title"><Translate>Payment details</Translate></div>
- <FormProvider<Paid> object={value} valueHandler={valueHandler}
>
- {/* <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`} />
- <TextField<Paid> name="order_status_url" label={i18n`Status
URL`} >
- <a target="_blank" rel="noreferrer"
href={order.order_status_url}>
- {order.order_status_url}
- </a>
- </TextField>
- </FormProvider>
+ <section class="section">
+ <div class="columns">
+ <div class="column is-4">
+ <div class="title">
+ <Translate>Timeline</Translate>
+ </div>
+ <Timeline events={events} />
+ </div>
+ <div class="column is-8">
+ <div class="title">
+ <Translate>Payment details</Translate>
+ </div>
+ <FormProvider<Paid>
+ object={value}
+ valueHandler={valueHandler}
+ >
+ {/* <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`}
+ />
+ <TextField<Paid>
+ name="order_status_url"
+ label={i18n`Status URL`}
+ >
+ <a
+ target="_blank"
+ rel="noreferrer"
+ href={order.order_status_url}
+ >
+ {order.order_status_url}
+ </a>
+ </TextField>
+ </FormProvider>
+ </div>
</div>
- </div>
- </section>
+ </section>
+ {order.contract_terms.products.length ? (
+ <Fragment>
+ <div class="title">
+ <Translate>Product list</Translate>
+ </div>
+ <ProductList list={order.contract_terms.products} />
+ </Fragment>
+ ) : 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}
/>}
+ {value.contract_terms && (
+ <ContractTerms value={value.contract_terms} />
+ )}
+ </div>
+ <div class="column" />
</div>
- <div class="column" />
- </div>
- </section>
- </div>
+ </section>
+ </div>
+ );
}
-function UnpaidPage({ id, order }: { id: string; order:
MerchantBackend.Orders.CheckPaymentUnpaidResponse }) {
- const [value, valueHandler] = useState<Partial<Unpaid>>(order)
- const i18n = useTranslator()
- return <div>
-
- <section class="hero is-hero-bar">
- <div class="hero-body">
- <div class="level">
- <div class="level-left">
- <div class="level-item">
- <h1 class="title">
- <Translate>Order</Translate> #{id}
- </h1>
+function UnpaidPage({
+ id,
+ order,
+}: {
+ id: string;
+ order: MerchantBackend.Orders.CheckPaymentUnpaidResponse;
+}) {
+ const [value, valueHandler] = useState<Partial<Unpaid>>(order);
+ const i18n = useTranslator();
+ return (
+ <div>
+ <section class="hero is-hero-bar">
+ <div class="hero-body">
+ <div class="level">
+ <div class="level-left">
+ <div class="level-item">
+ <h1 class="title">
+ <Translate>Order</Translate> #{id}
+ </h1>
+ </div>
+ <div class="tag is-dark">
+ <Translate>unpaid</Translate>
+ </div>
</div>
- <div class="tag is-dark"><Translate>unpaid</Translate></div>
</div>
- </div>
- <div class="level">
- <div class="level-left" style={{ maxWidth: '100%' }}>
- <div class="level-item" style={{ maxWidth: '100%' }}>
- <div class="content" style={{
- whiteSpace: 'nowrap',
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- }}>
- <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 class="level">
+ <div class="level-left" style={{ maxWidth: "100%" }}>
+ <div class="level-item" style={{ maxWidth: "100%" }}>
+ <div
+ class="content"
+ style={{
+ whiteSpace: "nowrap",
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ }}
+ >
+ <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_s),
+ "yyyy-MM-dd HH:mm:ss"
+ )}
+ </p>
+ </div>
</div>
</div>
</div>
</div>
- </div>
- </section>
-
- <section class="section is-main-section">
- <div class="columns">
- <div class="column" />
- <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`} />
- </FormProvider>
+ </section>
+
+ <section class="section is-main-section">
+ <div class="columns">
+ <div class="column" />
+ <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`}
+ />
+ </FormProvider>
+ </div>
+ <div class="column" />
</div>
- <div class="column" />
- </div>
- </section>
-
- </div>
+ </section>
+ </div>
+ );
}
export function DetailPage({ id, selected, onRefund, onBack }: Props): VNode {
- const [showRefund, setShowRefund] = useState<string | undefined>(undefined)
+ const [showRefund, setShowRefund] = useState<string | undefined>(undefined);
const DetailByStatus = function () {
switch (selected.order_status) {
- case 'claimed': return <ClaimedPage id={id} order={selected} />
- case 'paid': return <PaidPage id={id} order={selected}
onRefund={setShowRefund} />
- case 'unpaid': return <UnpaidPage id={id} order={selected} />
- default: return <div><Translate>Unknown order status. This is an error,
please contact the administrator.</Translate></div>
+ case "claimed":
+ return <ClaimedPage id={id} order={selected} />;
+ case "paid":
+ return <PaidPage id={id} order={selected} onRefund={setShowRefund} />;
+ case "unpaid":
+ return <UnpaidPage id={id} order={selected} />;
+ default:
+ return (
+ <div>
+ <Translate>
+ Unknown order status. This is an error, please contact the
+ administrator.
+ </Translate>
+ </div>
+ );
}
- }
-
- return <Fragment>
- {DetailByStatus()}
- {showRefund && <RefundModal
- order={selected}
- onCancel={() => setShowRefund(undefined)}
- onConfirm={(value) => {
- onRefund(showRefund, value)
- setShowRefund(undefined)
- }}
- />}
- <div class="columns">
- <div class="column" />
- <div class="column is-four-fifths">
- <div class="buttons is-right mt-5">
- <button class="button"
onClick={onBack}><Translate>Back</Translate></button>
+ };
+
+ return (
+ <Fragment>
+ {DetailByStatus()}
+ {showRefund && (
+ <RefundModal
+ order={selected}
+ onCancel={() => setShowRefund(undefined)}
+ onConfirm={(value) => {
+ onRefund(showRefund, value);
+ setShowRefund(undefined);
+ }}
+ />
+ )}
+ <div class="columns">
+ <div class="column" />
+ <div class="column is-four-fifths">
+ <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>
-
- </Fragment>
+ </Fragment>
+ );
}
async function copyToClipboard(text: string) {
- return navigator.clipboard.writeText(text)
+ return navigator.clipboard.writeText(text);
}
-
-
diff --git
a/packages/merchant-backoffice/src/paths/instance/orders/list/List.stories.tsx
b/packages/merchant-backoffice/src/paths/instance/orders/list/List.stories.tsx
index 996c0aa..c167fc6 100644
---
a/packages/merchant-backoffice/src/paths/instance/orders/list/List.stories.tsx
+++
b/packages/merchant-backoffice/src/paths/instance/orders/list/List.stories.tsx
@@ -15,83 +15,93 @@
*/
/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-
-import { h, VNode, FunctionalComponent } from 'preact';
-import { ListPage as TestedComponent } from './ListPage';
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+import { h, VNode, FunctionalComponent } from "preact";
+import { ListPage as TestedComponent } from "./ListPage";
export default {
- title: 'Pages/Order/List',
+ title: "Pages/Order/List",
component: TestedComponent,
argTypes: {
- onShowAll: { action: 'onShowAll' },
- onShowPaid: { action: 'onShowPaid' },
- onShowRefunded: { action: 'onShowRefunded' },
- onShowNotWired: { action: 'onShowNotWired' },
- onCopyURL: { action: 'onCopyURL' },
- onSelectDate: { action: 'onSelectDate' },
- onLoadMoreBefore: { action: 'onLoadMoreBefore' },
- onLoadMoreAfter: { action: 'onLoadMoreAfter' },
- onSelectOrder: { action: 'onSelectOrder' },
- onRefundOrder: { action: 'onRefundOrder' },
- onSearchOrderById: { action: 'onSearchOrderById' },
- onCreate: { action: 'onCreate' },
+ onShowAll: { action: "onShowAll" },
+ onShowPaid: { action: "onShowPaid" },
+ onShowRefunded: { action: "onShowRefunded" },
+ onShowNotWired: { action: "onShowNotWired" },
+ onCopyURL: { action: "onCopyURL" },
+ onSelectDate: { action: "onSelectDate" },
+ onLoadMoreBefore: { action: "onLoadMoreBefore" },
+ onLoadMoreAfter: { action: "onLoadMoreAfter" },
+ onSelectOrder: { action: "onSelectOrder" },
+ onRefundOrder: { action: "onRefundOrder" },
+ onSearchOrderById: { action: "onSearchOrderById" },
+ onCreate: { action: "onCreate" },
},
};
-function createExample<Props>(Component: FunctionalComponent<Props>, props:
Partial<Props>) {
- const r = (args: any) => <Component {...args} />
- r.args = props
- return r
+function createExample<Props>(
+ Component: FunctionalComponent<Props>,
+ props: Partial<Props>
+) {
+ const r = (args: any) => <Component {...args} />;
+ r.args = props;
+ return r;
}
export const Example = createExample(TestedComponent, {
- orders: [{
- id: '123',
- amount: 'TESTKUDOS:10',
- paid: false,
- refundable: true,
- row_id: 1,
- summary: 'summary',
- timestamp: {
- t_ms: new Date().getTime()
+ orders: [
+ {
+ id: "123",
+ amount: "TESTKUDOS:10",
+ paid: false,
+ refundable: true,
+ row_id: 1,
+ summary: "summary",
+ timestamp: {
+ t_s: new Date().getTime(),
+ },
+ order_id: "123",
},
- order_id: '123'
- },{
- id: '234',
- amount: 'TESTKUDOS:12',
- paid: true,
- refundable: true,
- row_id: 2,
- summary: 'summary with long text, very very long text that someone want to
add as a description of the order',
- timestamp: {
- t_ms: new Date().getTime()
+ {
+ id: "234",
+ amount: "TESTKUDOS:12",
+ paid: true,
+ refundable: true,
+ row_id: 2,
+ summary:
+ "summary with long text, very very long text that someone want to add
as a description of the order",
+ timestamp: {
+ t_s: new Date().getTime(),
+ },
+ order_id: "234",
},
- order_id: '234'
- },{
- id: '456',
- amount: 'TESTKUDOS:1',
- paid: false,
- refundable: false,
- row_id: 3,
- summary: 'summary with long text, very very long text that someone want to
add as a description of the order',
- timestamp: {
- t_ms: new Date().getTime()
+ {
+ id: "456",
+ amount: "TESTKUDOS:1",
+ paid: false,
+ refundable: false,
+ row_id: 3,
+ summary:
+ "summary with long text, very very long text that someone want to add
as a description of the order",
+ timestamp: {
+ t_s: new Date().getTime(),
+ },
+ order_id: "456",
},
- order_id: '456'
- },{
- id: '234',
- amount: 'TESTKUDOS:12',
- paid: false,
- refundable: false,
- row_id: 4,
- summary: 'summary with long text, very very long text that someone want to
add as a description of the order',
- timestamp: {
- t_ms: new Date().getTime()
+ {
+ id: "234",
+ amount: "TESTKUDOS:12",
+ paid: false,
+ refundable: false,
+ row_id: 4,
+ summary:
+ "summary with long text, very very long text that someone want to add
as a description of the order",
+ timestamp: {
+ t_s: new Date().getTime(),
+ },
+ order_id: "234",
},
- order_id: '234'
- }]
+ ],
});
diff --git
a/packages/merchant-backoffice/src/paths/instance/orders/list/Table.tsx
b/packages/merchant-backoffice/src/paths/instance/orders/list/Table.tsx
index 58b8064..571d674 100644
--- a/packages/merchant-backoffice/src/paths/instance/orders/list/Table.tsx
+++ b/packages/merchant-backoffice/src/paths/instance/orders/list/Table.tsx
@@ -15,14 +15,17 @@
*/
/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
import { format } from "date-fns";
import { h, VNode } from "preact";
import { StateUpdater, useState } from "preact/hooks";
-import { FormProvider, FormErrors } from
"../../../../components/form/FormProvider";
+import {
+ FormProvider,
+ FormErrors,
+} from "../../../../components/form/FormProvider";
import { Input } from "../../../../components/form/Input";
import { InputCurrency } from "../../../../components/form/InputCurrency";
import { InputGroup } from "../../../../components/form/InputGroup";
@@ -34,9 +37,9 @@ import { RefundSchema } from "../../../../schemas";
import { mergeRefunds } from "../../../../utils/amount";
import { Amounts } from "@gnu-taler/taler-util";
import { useConfigContext } from "../../../../context/config";
-import * as yup from 'yup';
+import * as yup from "yup";
-type Entity = MerchantBackend.Orders.OrderHistoryEntry & WithId
+type Entity = MerchantBackend.Orders.OrderHistoryEntry & WithId;
interface Props {
orders: Entity[];
onRefund: (value: Entity) => void;
@@ -49,43 +52,67 @@ interface Props {
onLoadMoreAfter?: () => void;
}
+export function CardTable({
+ orders,
+ onCreate,
+ onRefund,
+ onCopyURL,
+ onSelect,
+ onLoadMoreAfter,
+ onLoadMoreBefore,
+ hasMoreAfter,
+ hasMoreBefore,
+}: Props): VNode {
+ const [rowSelection, rowSelectionHandler] = useState<string[]>([]);
-export function CardTable({ orders, onCreate, onRefund, onCopyURL, onSelect,
onLoadMoreAfter, onLoadMoreBefore, hasMoreAfter, hasMoreBefore }: Props): VNode
{
- const [rowSelection, rowSelectionHandler] = useState<string[]>([])
-
- const i18n = useTranslator()
+ 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-cash-register" /></span><Translate>Orders</Translate></p>
+ return (
+ <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>Orders</Translate>
+ </p>
- <div class="card-header-icon" aria-label="more options" />
+ <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`create 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>
- <div class="card-content">
- <div class="b-table has-pagination">
- <div class="table-wrapper has-mobile-cards">
- {orders.length > 0 ?
- <Table instances={orders} onSelect={onSelect} onRefund={onRefund}
- onCopyURL={o => onCopyURL(o.id)}
- rowSelection={rowSelection}
rowSelectionHandler={rowSelectionHandler}
- onLoadMoreAfter={onLoadMoreAfter}
onLoadMoreBefore={onLoadMoreBefore}
- hasMoreAfter={hasMoreAfter} hasMoreBefore={hasMoreBefore}
- /> :
- <EmptyTable />
- }
+ <div class="card-header-icon" aria-label="more options">
+ <span class="has-tooltip-left" data-tooltip={i18n`create 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>
+ <div class="card-content">
+ <div class="b-table has-pagination">
+ <div class="table-wrapper has-mobile-cards">
+ {orders.length > 0 ? (
+ <Table
+ instances={orders}
+ onSelect={onSelect}
+ onRefund={onRefund}
+ onCopyURL={(o) => onCopyURL(o.id)}
+ rowSelection={rowSelection}
+ rowSelectionHandler={rowSelectionHandler}
+ onLoadMoreAfter={onLoadMoreAfter}
+ onLoadMoreBefore={onLoadMoreBefore}
+ hasMoreAfter={hasMoreAfter}
+ hasMoreBefore={hasMoreBefore}
+ />
+ ) : (
+ <EmptyTable />
+ )}
+ </div>
</div>
</div>
</div>
- </div>
+ );
}
interface TableProps {
rowSelection: string[];
@@ -100,130 +127,267 @@ interface TableProps {
onLoadMoreAfter?: () => void;
}
-function Table({ instances, onSelect, onRefund, onCopyURL, onLoadMoreAfter,
onLoadMoreBefore, hasMoreAfter, hasMoreBefore }: TableProps): VNode {
- return <div class="table-container">
- {onLoadMoreBefore && <button class="button is-fullwidth"
disabled={!hasMoreBefore} onClick={onLoadMoreBefore}><Translate>load newer
orders</Translate></button>}
- <table class="table is-striped is-hoverable is-fullwidth">
- <thead>
- <tr>
- <th style={{ minWidth: 100 }}><Translate>Date</Translate></th>
- <th style={{ minWidth: 100 }}><Translate>Amount</Translate></th>
- <th style={{ minWidth: 400 }}><Translate>Summary</Translate></th>
- <th style={{ minWidth: 50 }} />
- </tr>
- </thead>
- <tbody>
- {instances.map((i) => {
- return <tr key={i.id}>
- <td onClick={(): void => onSelect(i)} style={{ cursor: 'pointer'
}} >{format(new Date(i.timestamp.t_ms), 'yyyy/MM/dd HH:mm:ss')}</td>
- <td onClick={(): void => onSelect(i)} style={{ cursor: 'pointer'
}} >{i.amount}</td>
- <td onClick={(): void => onSelect(i)} style={{ cursor: 'pointer'
}} >{i.summary}</td>
- <td class="is-actions-cell right-sticky">
- <div class="buttons is-right">
- {(i.refundable) &&
- <button class="button is-small is-danger jb-modal"
type="button" onClick={(): void => onRefund(i)}>
- <Translate>Refund</Translate>
- </button>
- }
- {(!i.paid) &&
- <button class="button is-small is-info jb-modal"
type="button" onClick={(): void => onCopyURL(i)}>
- <Translate>copy url</Translate>
- </button>
- }
- </div>
- </td>
+function Table({
+ instances,
+ onSelect,
+ onRefund,
+ onCopyURL,
+ onLoadMoreAfter,
+ onLoadMoreBefore,
+ hasMoreAfter,
+ hasMoreBefore,
+}: TableProps): VNode {
+ return (
+ <div class="table-container">
+ {onLoadMoreBefore && (
+ <button
+ class="button is-fullwidth"
+ disabled={!hasMoreBefore}
+ onClick={onLoadMoreBefore}
+ >
+ <Translate>load newer orders</Translate>
+ </button>
+ )}
+ <table class="table is-striped is-hoverable is-fullwidth">
+ <thead>
+ <tr>
+ <th style={{ minWidth: 100 }}>
+ <Translate>Date</Translate>
+ </th>
+ <th style={{ minWidth: 100 }}>
+ <Translate>Amount</Translate>
+ </th>
+ <th style={{ minWidth: 400 }}>
+ <Translate>Summary</Translate>
+ </th>
+ <th style={{ minWidth: 50 }} />
</tr>
- })}
- </tbody>
- </table>
- {onLoadMoreAfter && <button class="button is-fullwidth"
disabled={!hasMoreAfter} onClick={onLoadMoreAfter}><Translate>load older
orders</Translate></button>}
- </div>
+ </thead>
+ <tbody>
+ {instances.map((i) => {
+ return (
+ <tr key={i.id}>
+ <td
+ onClick={(): void => onSelect(i)}
+ style={{ cursor: "pointer" }}
+ >
+ {format(new Date(i.timestamp.t_s), "yyyy/MM/dd HH:mm:ss")}
+ </td>
+ <td
+ onClick={(): void => onSelect(i)}
+ style={{ cursor: "pointer" }}
+ >
+ {i.amount}
+ </td>
+ <td
+ onClick={(): void => onSelect(i)}
+ style={{ cursor: "pointer" }}
+ >
+ {i.summary}
+ </td>
+ <td class="is-actions-cell right-sticky">
+ <div class="buttons is-right">
+ {i.refundable && (
+ <button
+ class="button is-small is-danger jb-modal"
+ type="button"
+ onClick={(): void => onRefund(i)}
+ >
+ <Translate>Refund</Translate>
+ </button>
+ )}
+ {!i.paid && (
+ <button
+ class="button is-small is-info jb-modal"
+ type="button"
+ onClick={(): void => onCopyURL(i)}
+ >
+ <Translate>copy url</Translate>
+ </button>
+ )}
+ </div>
+ </td>
+ </tr>
+ );
+ })}
+ </tbody>
+ </table>
+ {onLoadMoreAfter && (
+ <button
+ class="button is-fullwidth"
+ disabled={!hasMoreAfter}
+ onClick={onLoadMoreAfter}
+ >
+ <Translate>load older orders</Translate>
+ </button>
+ )}
+ </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 orders have been found matching your
query!</Translate></p>
- </div>
+ 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 orders have been found matching your query!</Translate>
+ </p>
+ </div>
+ );
}
interface RefundModalProps {
onCancel: () => void;
onConfirm: (value: MerchantBackend.Orders.RefundRequest) => void;
- order: MerchantBackend.Orders.MerchantOrderStatusResponse
+ order: MerchantBackend.Orders.MerchantOrderStatusResponse;
}
-export function RefundModal({ order, onCancel, onConfirm }: RefundModalProps):
VNode {
- type State = { mainReason?: string, description?: string, refund?: string }
- const [form, setValue] = useState<State>({})
+export function RefundModal({
+ order,
+ onCancel,
+ onConfirm,
+}: RefundModalProps): VNode {
+ type State = { mainReason?: string; description?: string; refund?: string };
+ const [form, setValue] = useState<State>({});
const i18n = useTranslator();
- const [errors, setErrors] = useState<FormErrors<State>>({})
+ const [errors, setErrors] = useState<FormErrors<State>>({});
const validateAndConfirm = () => {
try {
- RefundSchema.validateSync(form, { abortEarly: false })
+ RefundSchema.validateSync(form, { abortEarly: false });
if (!form.refund) return;
onConfirm({
refund: form.refund,
- reason: `${form.mainReason}: ${form.description}`
- })
+ reason: `${form.mainReason}: ${form.description}`,
+ });
} catch (err) {
if (err instanceof yup.ValidationError) {
- const errors = err.inner as any[]
- const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev :
({ ...prev, [cur.path]: cur.message }), {})
- setErrors(pathMessages)
+ const errors = err.inner as any[];
+ const pathMessages = errors.reduce(
+ (prev, cur) =>
+ !cur.path ? prev : { ...prev, [cur.path]: cur.message },
+ {}
+ );
+ setErrors(pathMessages);
} else {
- console.log(err)
+ console.log(err);
}
}
- }
+ };
- const refunds = (order.order_status === 'paid' ? order.refund_details :
[]).reduce(mergeRefunds, [])
+ const refunds = (
+ order.order_status === "paid" ? order.refund_details : []
+ ).reduce(mergeRefunds, []);
- 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 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)
+ 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">
- <div class="column is-2" />
- <div class="column is-8">
- <InputGroup name="asd" label={`${totalRefunded} was already refunded`}>
- <table class="table is-fullwidth">
- <thead>
- <tr>
- <th><Translate>date</Translate></th>
- <th><Translate>amount</Translate></th>
- <th><Translate>reason</Translate></th>
- </tr>
- </thead>
- <tbody>
- {refunds.map(r => {
- return <tr key={r.timestamp.t_ms}>
- <td>{format(new Date(r.timestamp.t_ms), 'yyyy-MM-dd
HH:mm:ss')}</td>
- <td>{r.amount}</td>
- <td>{r.reason}</td>
- </tr>
- })}
- </tbody>
- </table>
- </InputGroup>
- </div>
- <div class="column is-2" />
- </div>}
-
- {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>
{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`} />}
- </FormProvider>}
-
- </ConfirmModal>
+ return (
+ <ConfirmModal
+ description="refund"
+ danger
+ active
+ onCancel={onCancel}
+ onConfirm={validateAndConfirm}
+ >
+ {refunds.length > 0 && (
+ <div class="columns">
+ <div class="column is-2" />
+ <div class="column is-8">
+ <InputGroup
+ name="asd"
+ label={`${totalRefunded} was already refunded`}
+ >
+ <table class="table is-fullwidth">
+ <thead>
+ <tr>
+ <th>
+ <Translate>date</Translate>
+ </th>
+ <th>
+ <Translate>amount</Translate>
+ </th>
+ <th>
+ <Translate>reason</Translate>
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ {refunds.map((r) => {
+ return (
+ <tr key={r.timestamp.t_s}>
+ <td>
+ {format(
+ new Date(r.timestamp.t_s),
+ "yyyy-MM-dd HH:mm:ss"
+ )}
+ </td>
+ <td>{r.amount}</td>
+ <td>{r.reason}</td>
+ </tr>
+ );
+ })}
+ </tbody>
+ </table>
+ </InputGroup>
+ </div>
+ <div class="column is-2" />
+ </div>
+ )}
+
+ {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>{" "}
+ {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`}
+ />
+ )}
+ </FormProvider>
+ )}
+ </ConfirmModal>
+ );
}
diff --git
a/packages/merchant-backoffice/src/paths/instance/products/list/Table.tsx
b/packages/merchant-backoffice/src/paths/instance/products/list/Table.tsx
index 391fb63..6d082cb 100644
--- a/packages/merchant-backoffice/src/paths/instance/products/list/Table.tsx
+++ b/packages/merchant-backoffice/src/paths/instance/products/list/Table.tsx
@@ -15,146 +15,290 @@
*/
/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-
-import { format } from "date-fns"
-import { ComponentChildren, Fragment, h, VNode } from "preact"
-import { StateUpdater, useState } from "preact/hooks"
-import { FormProvider, FormErrors } from
"../../../../components/form/FormProvider"
-import { InputCurrency } from "../../../../components/form/InputCurrency"
-import { InputNumber } from "../../../../components/form/InputNumber"
-import { MerchantBackend, WithId } from "../../../../declaration"
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { format } from "date-fns";
+import { ComponentChildren, Fragment, h, VNode } from "preact";
+import { StateUpdater, useState } from "preact/hooks";
+import {
+ FormProvider,
+ FormErrors,
+} from "../../../../components/form/FormProvider";
+import { InputCurrency } from "../../../../components/form/InputCurrency";
+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"
+import { Translate, useTranslator } from "../../../../i18n";
+import { Amounts } from "@gnu-taler/taler-util";
-type Entity = MerchantBackend.Products.ProductDetail & WithId
+type Entity = MerchantBackend.Products.ProductDetail & WithId;
interface Props {
instances: Entity[];
onDelete: (id: Entity) => void;
onSelect: (product: Entity) => void;
- onUpdate: (id: string, data: MerchantBackend.Products.ProductPatchDetail) =>
Promise<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">
- <span class="has-tooltip-left" data-tooltip={i18n`add product to
inventory`}>
- <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">
- <div class="b-table has-pagination">
- <div class="table-wrapper has-mobile-cards">
- {instances.length > 0 ?
- <Table instances={instances} onSelect={onSelect}
onDelete={onDelete} onUpdate={onUpdate} rowSelection={rowSelection}
rowSelectionHandler={rowSelectionHandler} /> :
- <EmptyTable />
- }
+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">
+ <span
+ class="has-tooltip-left"
+ data-tooltip={i18n`add product to inventory`}
+ >
+ <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">
+ <div class="b-table has-pagination">
+ <div class="table-wrapper has-mobile-cards">
+ {instances.length > 0 ? (
+ <Table
+ instances={instances}
+ onSelect={onSelect}
+ onDelete={onDelete}
+ onUpdate={onUpdate}
+ rowSelection={rowSelection}
+ rowSelectionHandler={rowSelectionHandler}
+ />
+ ) : (
+ <EmptyTable />
+ )}
+ </div>
</div>
</div>
</div>
- </div>
+ );
}
interface TableProps {
rowSelection: string | undefined;
instances: Entity[];
onSelect: (id: Entity) => void;
- onUpdate: (id: string, data: MerchantBackend.Products.ProductPatchDetail) =>
Promise<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()
+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">
<thead>
<tr>
- <th><Translate>Image</Translate></th>
- <th><Translate>Description</Translate></th>
- <th><Translate>Sell</Translate></th>
- <th><Translate>Taxes</Translate></th>
- <th><Translate>Profit</Translate></th>
- <th><Translate>Stock</Translate></th>
- <th><Translate>Sold</Translate></th>
+ <th>
+ <Translate>Image</Translate>
+ </th>
+ <th>
+ <Translate>Description</Translate>
+ </th>
+ <th>
+ <Translate>Sell</Translate>
+ </th>
+ <th>
+ <Translate>Taxes</Translate>
+ </th>
+ <th>
+ <Translate>Profit</Translate>
+ </th>
+ <th>
+ <Translate>Stock</Translate>
+ </th>
+ <th>
+ <Translate>Sold</Translate>
+ </th>
<th />
</tr>
</thead>
<tbody>
- {instances.map(i => {
-
- const restStockInfo = !i.next_restock ? '' : (
- i.next_restock.t_ms === 'never' ?
- 'never' :
- `restock at ${format(new Date(i.next_restock.t_ms),
'yyyy/MM/dd')}`
- )
- let stockInfo: ComponentChildren = '';
+ {instances.map((i) => {
+ const restStockInfo = !i.next_restock
+ ? ""
+ : i.next_restock.t_s === "never"
+ ? "never"
+ : `restock at ${format(
+ new Date(i.next_restock.t_s),
+ "yyyy/MM/dd"
+ )}`;
+ let stockInfo: ComponentChildren = "";
if (i.total_stock < 0) {
- stockInfo = 'infinite'
+ stockInfo = "infinite";
} else {
- const totalStock = i.total_stock - i.total_lost - i.total_sold
- stockInfo = <label title={restStockInfo}>{totalStock}
{i.unit}</label>
+ const totalStock = i.total_stock - i.total_lost - i.total_sold;
+ 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' }} >
- {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">
- <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).then(r => rowSelectionHandler(undefined))} onCancel={() =>
rowSelectionHandler(undefined)} />
- </td>
- </tr>}
- </Fragment>
- })}
+ 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" }}
+ >
+ {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">
+ <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).then((r) =>
+ rowSelectionHandler(undefined)
+ )
+ }
+ onCancel={() => rowSelectionHandler(undefined)}
+ />
+ </td>
+ </tr>
+ )}
+ </Fragment>
+ );
+ })}
</tbody>
</table>
- </div>)
+ </div>
+ );
}
interface FastProductUpdateFormProps {
product: Entity;
- onUpdate: (data: MerchantBackend.Products.ProductPatchDetail) =>
Promise<void>;
+ onUpdate: (
+ data: MerchantBackend.Products.ProductPatchDetail
+ ) => Promise<void>;
onCancel: () => void;
}
interface FastProductUpdate {
@@ -166,90 +310,170 @@ interface UpdatePrice {
price: string;
}
-function FastProductWithInfiniteStockUpdateForm({ product, onUpdate, onCancel
}: FastProductUpdateFormProps) {
- const [value, valueHandler] = useState<UpdatePrice>({ price: product.price })
- const i18n = useTranslator()
-
- return <Fragment>
- <FormProvider<FastProductUpdate> name="added" object={value}
valueHandler={valueHandler as any} >
- <InputCurrency<FastProductUpdate> name="price" label={i18n`Price`}
tooltip={i18n`update the product with new price`} />
- </FormProvider>
-
- <div class="buttons is-right mt-5">
- <button class="button" onClick={onCancel}
><Translate>Cancel</Translate></button>
- <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>
- </span>
- </div>
+function FastProductWithInfiniteStockUpdateForm({
+ product,
+ onUpdate,
+ onCancel,
+}: FastProductUpdateFormProps) {
+ const [value, valueHandler] = useState<UpdatePrice>({ price: product.price
});
+ const i18n = useTranslator();
+
+ return (
+ <Fragment>
+ <FormProvider<FastProductUpdate>
+ name="added"
+ object={value}
+ valueHandler={valueHandler as any}
+ >
+ <InputCurrency<FastProductUpdate>
+ name="price"
+ label={i18n`Price`}
+ tooltip={i18n`update the product with new price`}
+ />
+ </FormProvider>
- </Fragment>
+ <div class="buttons is-right mt-5">
+ <button class="button" onClick={onCancel}>
+ <Translate>Cancel</Translate>
+ </button>
+ <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>
+ </span>
+ </div>
+ </Fragment>
+ );
}
-function FastProductWithManagedStockUpdateForm({ product, onUpdate, onCancel
}: FastProductUpdateFormProps) {
+function FastProductWithManagedStockUpdateForm({
+ product,
+ onUpdate,
+ onCancel,
+}: FastProductUpdateFormProps) {
const [value, valueHandler] = useState<FastProductUpdate>({
- incoming: 0, lost: 0, price: product.price
- })
+ incoming: 0,
+ lost: 0,
+ price: product.price,
+ });
- const currentStock = product.total_stock - product.total_sold -
product.total_lost
+ const currentStock =
+ product.total_stock - product.total_sold - product.total_lost;
const errors: FormErrors<FastProductUpdate> = {
- lost: currentStock + value.incoming < value.lost ?
- `lost cannot be greater that current + incoming (max ${currentStock +
value.incoming})`
- : undefined
- }
-
- const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !==
undefined)
- const i18n = useTranslator()
-
- return <Fragment>
- <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`} />
- <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>
- <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>
- </span>
- </div>
+ lost:
+ currentStock + value.incoming < value.lost
+ ? `lost cannot be greater that current + incoming (max ${
+ currentStock + value.incoming
+ })`
+ : undefined,
+ };
- </Fragment>
+ const hasErrors = Object.keys(errors).some(
+ (k) => (errors as any)[k] !== undefined
+ );
+ const i18n = useTranslator();
+
+ return (
+ <Fragment>
+ <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`}
+ />
+ <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>
+ <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>
+ </span>
+ </div>
+ </Fragment>
+ );
}
function FastProductUpdateForm(props: FastProductUpdateFormProps) {
- return props.product.total_stock === -1 ?
- <FastProductWithInfiniteStockUpdateForm {...props} /> :
+ return props.product.total_stock === -1 ? (
+ <FastProductWithInfiniteStockUpdateForm {...props} />
+ ) : (
<FastProductWithManagedStockUpdateForm {...props} />
+ );
}
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>There is no products yet, add more pressing the +
sign</Translate></p>
- </div>
+ 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>
+ There is no products yet, add more pressing the + sign
+ </Translate>
+ </p>
+ </div>
+ );
}
-
function difference(price: string, tax: number) {
if (!tax) return price;
- const ps = price.split(':')
- const p = parseInt(ps[1], 10)
- ps[1] = `${p - tax}`
- return ps.join(':')
+ const ps = price.split(":");
+ const p = parseInt(ps[1], 10);
+ ps[1] = `${p - tax}`;
+ return ps.join(":");
}
function sum(taxes: MerchantBackend.Tax[]) {
- return taxes.reduce((p, c) => p + parseInt(c.tax.split(':')[1], 10), 0)
+ return taxes.reduce((p, c) => p + parseInt(c.tax.split(":")[1], 10), 0);
}
diff --git
a/packages/merchant-backoffice/src/paths/instance/reserves/details/DetailPage.tsx
b/packages/merchant-backoffice/src/paths/instance/reserves/details/DetailPage.tsx
index 64d7a3a..59041e7 100644
---
a/packages/merchant-backoffice/src/paths/instance/reserves/details/DetailPage.tsx
+++
b/packages/merchant-backoffice/src/paths/instance/reserves/details/DetailPage.tsx
@@ -251,9 +251,9 @@ function TipRow({
<td>{info.total_picked_up}</td>
<td>{info.reason}</td>
<td>
- {info.expiration.t_ms === "never"
+ {info.expiration.t_s === "never"
? "never"
- : format(info.expiration.t_ms, "yyyy/MM/dd HH:mm:ss")}
+ : format(info.expiration.t_s, "yyyy/MM/dd HH:mm:ss")}
</td>
</tr>
);
diff --git
a/packages/merchant-backoffice/src/paths/instance/reserves/details/Details.stories.tsx
b/packages/merchant-backoffice/src/paths/instance/reserves/details/Details.stories.tsx
index c62442a..c114ded 100644
---
a/packages/merchant-backoffice/src/paths/instance/reserves/details/Details.stories.tsx
+++
b/packages/merchant-backoffice/src/paths/instance/reserves/details/Details.stories.tsx
@@ -15,88 +15,91 @@
*/
/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-
-import { h, VNode, FunctionalComponent } from 'preact';
-import { DetailPage as TestedComponent } from './DetailPage';
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+import { h, VNode, FunctionalComponent } from "preact";
+import { DetailPage as TestedComponent } from "./DetailPage";
export default {
- title: 'Pages/Reserve/Detail',
+ title: "Pages/Reserve/Detail",
component: TestedComponent,
argTypes: {
- onUpdate: { action: 'onUpdate' },
- onBack: { action: 'onBack' },
+ onUpdate: { action: "onUpdate" },
+ onBack: { action: "onBack" },
},
};
-function createExample<Props>(Component: FunctionalComponent<Props>, props:
Partial<Props>) {
- const r = (args: any) => <Component {...args} />
- r.args = props
- return r
+function createExample<Props>(
+ Component: FunctionalComponent<Props>,
+ props: Partial<Props>
+) {
+ const r = (args: any) => <Component {...args} />;
+ r.args = props;
+ return r;
}
export const Funded = createExample(TestedComponent, {
- id:'THISISTHERESERVEID',
+ id: "THISISTHERESERVEID",
selected: {
active: true,
- committed_amount: 'TESTKUDOS:10',
+ committed_amount: "TESTKUDOS:10",
creation_time: {
- t_ms: new Date().getTime(),
+ t_s: new Date().getTime(),
},
- exchange_initial_amount: 'TESTKUDOS:10',
+ exchange_initial_amount: "TESTKUDOS:10",
expiration_time: {
- t_ms: new Date().getTime()
+ t_s: new Date().getTime(),
},
- merchant_initial_amount: 'TESTKUDOS:10',
- pickup_amount: 'TESTKUDOS:10',
- payto_uri: 'payto://x-taler-bank/bank.taler:8080/account',
- exchange_url: 'http://exchange.taler/',
- }
+ merchant_initial_amount: "TESTKUDOS:10",
+ pickup_amount: "TESTKUDOS:10",
+ payto_uri: "payto://x-taler-bank/bank.taler:8080/account",
+ exchange_url: "http://exchange.taler/",
+ },
});
export const NotYetFunded = createExample(TestedComponent, {
- id:'THISISTHERESERVEID',
+ id: "THISISTHERESERVEID",
selected: {
active: true,
- committed_amount: 'TESTKUDOS:10',
+ committed_amount: "TESTKUDOS:10",
creation_time: {
- t_ms: new Date().getTime(),
+ t_s: new Date().getTime(),
},
- exchange_initial_amount: 'TESTKUDOS:0',
+ exchange_initial_amount: "TESTKUDOS:0",
expiration_time: {
- t_ms: new Date().getTime()
+ t_s: new Date().getTime(),
},
- merchant_initial_amount: 'TESTKUDOS:10',
- pickup_amount: 'TESTKUDOS:10',
- payto_uri: 'payto://x-taler-bank/bank.taler:8080/account',
- exchange_url: 'http://exchange.taler/',
- }
+ merchant_initial_amount: "TESTKUDOS:10",
+ pickup_amount: "TESTKUDOS:10",
+ payto_uri: "payto://x-taler-bank/bank.taler:8080/account",
+ exchange_url: "http://exchange.taler/",
+ },
});
export const FundedWithEmptyTips = createExample(TestedComponent, {
- id:'THISISTHERESERVEID',
+ id: "THISISTHERESERVEID",
selected: {
active: true,
- committed_amount: 'TESTKUDOS:10',
+ committed_amount: "TESTKUDOS:10",
creation_time: {
- t_ms: new Date().getTime(),
+ t_s: new Date().getTime(),
},
- exchange_initial_amount: 'TESTKUDOS:10',
+ exchange_initial_amount: "TESTKUDOS:10",
expiration_time: {
- t_ms: new Date().getTime()
+ t_s: new Date().getTime(),
},
- merchant_initial_amount: 'TESTKUDOS:10',
- pickup_amount: 'TESTKUDOS:10',
- payto_uri: 'payto://x-taler-bank/bank.taler:8080/account',
- exchange_url: 'http://exchange.taler/',
- tips:[{
- reason: 'asdasd',
- tip_id: '123',
- total_amount: 'TESTKUDOS:1'
- }]
- }
+ merchant_initial_amount: "TESTKUDOS:10",
+ pickup_amount: "TESTKUDOS:10",
+ payto_uri: "payto://x-taler-bank/bank.taler:8080/account",
+ exchange_url: "http://exchange.taler/",
+ tips: [
+ {
+ reason: "asdasd",
+ tip_id: "123",
+ total_amount: "TESTKUDOS:1",
+ },
+ ],
+ },
});
-
diff --git
a/packages/merchant-backoffice/src/paths/instance/reserves/list/CreatedSuccessfully.tsx
b/packages/merchant-backoffice/src/paths/instance/reserves/list/CreatedSuccessfully.tsx
index 7e8dd2c..9a64d71 100644
---
a/packages/merchant-backoffice/src/paths/instance/reserves/list/CreatedSuccessfully.tsx
+++
b/packages/merchant-backoffice/src/paths/instance/reserves/list/CreatedSuccessfully.tsx
@@ -22,60 +22,76 @@ type Entity = MerchantBackend.Tips.TipCreateConfirmation;
interface Props {
entity: Entity;
- request: MerchantBackend.Tips.TipCreateRequest,
+ request: MerchantBackend.Tips.TipCreateRequest;
onConfirm: () => void;
onCreateAnother?: () => void;
}
-export function CreatedSuccessfully({ request, entity, onConfirm,
onCreateAnother }: Props): VNode {
- return <Fragment>
- <div class="field is-horizontal">
- <div class="field-label is-normal">
- <label class="label">Amount</label>
- </div>
- <div class="field-body is-flex-grow-3">
- <div class="field">
- <p class="control">
- <input readonly class="input" value={request.amount} />
- </p>
+export function CreatedSuccessfully({
+ request,
+ entity,
+ onConfirm,
+ onCreateAnother,
+}: Props): VNode {
+ return (
+ <Fragment>
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label">Amount</label>
</div>
- </div>
- </div>
- <div class="field is-horizontal">
- <div class="field-label is-normal">
- <label class="label">Justification</label>
- </div>
- <div class="field-body is-flex-grow-3">
- <div class="field">
- <p class="control">
- <input readonly class="input" value={request.justification} />
- </p>
+ <div class="field-body is-flex-grow-3">
+ <div class="field">
+ <p class="control">
+ <input readonly class="input" value={request.amount} />
+ </p>
+ </div>
</div>
</div>
- </div>
- <div class="field is-horizontal">
- <div class="field-label is-normal">
- <label class="label">URL</label>
- </div>
- <div class="field-body is-flex-grow-3">
- <div class="field">
- <p class="control">
- <input readonly class="input" value={entity.tip_status_url} />
- </p>
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label">Justification</label>
+ </div>
+ <div class="field-body is-flex-grow-3">
+ <div class="field">
+ <p class="control">
+ <input readonly class="input" value={request.justification} />
+ </p>
+ </div>
</div>
</div>
- </div>
- <div class="field is-horizontal">
- <div class="field-label is-normal">
- <label class="label">Valid until</label>
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label">URL</label>
+ </div>
+ <div class="field-body is-flex-grow-3">
+ <div class="field">
+ <p class="control">
+ <input readonly class="input" value={entity.tip_status_url} />
+ </p>
+ </div>
+ </div>
</div>
- <div class="field-body is-flex-grow-3">
- <div class="field">
- <p class="control">
- <input class="input" readonly value={!entity.tip_expiration ||
entity.tip_expiration.t_ms === "never" ? "never" :
format(entity.tip_expiration.t_ms, 'yyyy/MM/dd HH:mm:ss')} />
- </p>
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label">Valid until</label>
+ </div>
+ <div class="field-body is-flex-grow-3">
+ <div class="field">
+ <p class="control">
+ <input
+ class="input"
+ readonly
+ value={
+ !entity.tip_expiration ||
+ entity.tip_expiration.t_s === "never"
+ ? "never"
+ : format(entity.tip_expiration.t_s, "yyyy/MM/dd HH:mm:ss")
+ }
+ />
+ </p>
+ </div>
</div>
</div>
- </div>
- </Fragment>;
+ </Fragment>
+ );
}
diff --git
a/packages/merchant-backoffice/src/paths/instance/reserves/list/List.stories.tsx
b/packages/merchant-backoffice/src/paths/instance/reserves/list/List.stories.tsx
index b941066..79c91e7 100644
---
a/packages/merchant-backoffice/src/paths/instance/reserves/list/List.stories.tsx
+++
b/packages/merchant-backoffice/src/paths/instance/reserves/list/List.stories.tsx
@@ -15,83 +15,88 @@
*/
/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-
-import { h, VNode, FunctionalComponent } from 'preact';
-import { CardTable as TestedComponent } from './Table';
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+import { h, VNode, FunctionalComponent } from "preact";
+import { CardTable as TestedComponent } from "./Table";
export default {
- title: 'Pages/Reserve/List',
+ title: "Pages/Reserve/List",
component: TestedComponent,
argTypes: {
- onCreate: { action: 'onCreate' },
- onDelete: { action: 'onDelete' },
- onNewTip: { action: 'onNewTip' },
- onSelect: { action: 'onSelect' },
+ onCreate: { action: "onCreate" },
+ onDelete: { action: "onDelete" },
+ onNewTip: { action: "onNewTip" },
+ onSelect: { action: "onSelect" },
},
};
-function createExample<Props>(Component: FunctionalComponent<Props>, props:
Partial<Props>) {
- const r = (args: any) => <Component {...args} />
- r.args = props
- return r
+function createExample<Props>(
+ Component: FunctionalComponent<Props>,
+ props: Partial<Props>
+) {
+ const r = (args: any) => <Component {...args} />;
+ r.args = props;
+ return r;
}
export const AllFunded = createExample(TestedComponent, {
- instances: [{
- id: 'reseverId',
- active: true,
- committed_amount: 'TESTKUDOS:10',
- creation_time: {
- t_ms: new Date().getTime(),
- },
- exchange_initial_amount: 'TESTKUDOS:10',
- expiration_time: {
- t_ms: new Date().getTime()
- },
- merchant_initial_amount: 'TESTKUDOS:10',
- pickup_amount: 'TESTKUDOS:10',
- reserve_pub: 'WEQWDASDQWEASDADASDQWEQWEASDAS'
- },{
- id: 'reseverId2',
- active: true,
- committed_amount: 'TESTKUDOS:13',
- creation_time: {
- t_ms: new Date().getTime(),
+ instances: [
+ {
+ id: "reseverId",
+ active: true,
+ committed_amount: "TESTKUDOS:10",
+ creation_time: {
+ t_s: new Date().getTime(),
+ },
+ exchange_initial_amount: "TESTKUDOS:10",
+ expiration_time: {
+ t_s: new Date().getTime(),
+ },
+ merchant_initial_amount: "TESTKUDOS:10",
+ pickup_amount: "TESTKUDOS:10",
+ reserve_pub: "WEQWDASDQWEASDADASDQWEQWEASDAS",
},
- exchange_initial_amount: 'TESTKUDOS:10',
- expiration_time: {
- t_ms: new Date().getTime()
+ {
+ id: "reseverId2",
+ active: true,
+ committed_amount: "TESTKUDOS:13",
+ creation_time: {
+ t_s: new Date().getTime(),
+ },
+ exchange_initial_amount: "TESTKUDOS:10",
+ expiration_time: {
+ t_s: new Date().getTime(),
+ },
+ merchant_initial_amount: "TESTKUDOS:10",
+ pickup_amount: "TESTKUDOS:10",
+ reserve_pub: "WEQWDASDQWEASDADASDQWEQWEASDAS",
},
- merchant_initial_amount: 'TESTKUDOS:10',
- pickup_amount: 'TESTKUDOS:10',
- reserve_pub: 'WEQWDASDQWEASDADASDQWEQWEASDAS'
- }]
+ ],
});
export const Empty = createExample(TestedComponent, {
- instances: []
+ instances: [],
});
-
-
export const OneNotYetFunded = createExample(TestedComponent, {
- instances: [{
- id: 'reseverId',
- active: true,
- committed_amount: 'TESTKUDOS:0',
- creation_time: {
- t_ms: new Date().getTime(),
- },
- exchange_initial_amount: 'TESTKUDOS:0',
- expiration_time: {
- t_ms: new Date().getTime()
+ instances: [
+ {
+ id: "reseverId",
+ active: true,
+ committed_amount: "TESTKUDOS:0",
+ creation_time: {
+ t_s: new Date().getTime(),
+ },
+ exchange_initial_amount: "TESTKUDOS:0",
+ expiration_time: {
+ t_s: new Date().getTime(),
+ },
+ merchant_initial_amount: "TESTKUDOS:10",
+ pickup_amount: "TESTKUDOS:10",
+ reserve_pub: "WEQWDASDQWEASDADASDQWEQWEASDAS",
},
- merchant_initial_amount: 'TESTKUDOS:10',
- pickup_amount: 'TESTKUDOS:10',
- reserve_pub: 'WEQWDASDQWEASDADASDQWEQWEASDAS'
- }]
+ ],
});
diff --git
a/packages/merchant-backoffice/src/paths/instance/reserves/list/Table.tsx
b/packages/merchant-backoffice/src/paths/instance/reserves/list/Table.tsx
index a76edf6..fd1d2e3 100644
--- a/packages/merchant-backoffice/src/paths/instance/reserves/list/Table.tsx
+++ b/packages/merchant-backoffice/src/paths/instance/reserves/list/Table.tsx
@@ -15,16 +15,16 @@
*/
/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
-import { format } from "date-fns"
-import { Fragment, h, VNode } from "preact"
-import { MerchantBackend, WithId } from "../../../../declaration"
-import { Translate, useTranslator } from "../../../../i18n"
+import { format } from "date-fns";
+import { Fragment, h, VNode } from "preact";
+import { MerchantBackend, WithId } from "../../../../declaration";
+import { Translate, useTranslator } from "../../../../i18n";
-type Entity = MerchantBackend.Tips.ReserveStatusEntry & WithId
+type Entity = MerchantBackend.Tips.ReserveStatusEntry & WithId;
interface Props {
instances: Entity[];
@@ -34,59 +34,90 @@ interface Props {
onCreate: () => void;
}
-export function CardTable({ instances, onCreate, onSelect, onNewTip, onDelete
}: Props): VNode {
-
+export function CardTable({
+ instances,
+ onCreate,
+ onSelect,
+ onNewTip,
+ onDelete,
+}: Props): VNode {
const [withoutFunds, withFunds] = instances.reduce((prev, current) => {
- const amount = current.exchange_initial_amount
- if (amount.endsWith(':0')) {
- prev[0] = prev[0].concat(current)
+ const amount = current.exchange_initial_amount;
+ if (amount.endsWith(":0")) {
+ prev[0] = prev[0].concat(current);
} else {
- prev[1] = prev[1].concat(current)
+ prev[1] = prev[1].concat(current);
}
- return prev
- }, new Array<Array<Entity>>([], []))
+ return prev;
+ }, new Array<Array<Entity>>([], []));
- const i18n = useTranslator()
+ const i18n = useTranslator();
- return <Fragment>
- {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>
- </header>
- <div class="card-content">
- <div class="b-table has-pagination">
- <div class="table-wrapper has-mobile-cards">
- <TableWithoutFund instances={withoutFunds} onNewTip={onNewTip}
onSelect={onSelect} onDelete={onDelete} />
+ return (
+ <Fragment>
+ {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>
+ </header>
+ <div class="card-content">
+ <div class="b-table has-pagination">
+ <div class="table-wrapper has-mobile-cards">
+ <TableWithoutFund
+ instances={withoutFunds}
+ onNewTip={onNewTip}
+ onSelect={onSelect}
+ onDelete={onDelete}
+ />
+ </div>
+ </div>
</div>
</div>
- </div>
- </div>}
+ )}
- <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 ready</Translate></p>
- <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 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">
- <div class="b-table has-pagination">
- <div class="table-wrapper has-mobile-cards">
- {withFunds.length > 0 ?
- <Table instances={withFunds} onNewTip={onNewTip}
onSelect={onSelect} onDelete={onDelete} /> :
- <EmptyTable />
- }
+ <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 ready</Translate>
+ </p>
+ <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
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">
+ <div class="b-table has-pagination">
+ <div class="table-wrapper has-mobile-cards">
+ {withFunds.length > 0 ? (
+ <Table
+ instances={withFunds}
+ onNewTip={onNewTip}
+ onSelect={onSelect}
+ onDelete={onDelete}
+ />
+ ) : (
+ <EmptyTable />
+ )}
+ </div>
</div>
</div>
</div>
- </div>
- </Fragment>
+ </Fragment>
+ );
}
interface TableProps {
instances: Entity[];
@@ -96,89 +127,181 @@ interface TableProps {
}
function Table({ instances, onNewTip, onSelect, onDelete }: TableProps): VNode
{
- const i18n = useTranslator()
+ const i18n = useTranslator();
return (
<div class="table-container">
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
<thead>
<tr>
- <th><Translate>Created at</Translate></th>
- <th><Translate>Expires at</Translate></th>
- <th><Translate>Initial</Translate></th>
- <th><Translate>Picked up</Translate></th>
- <th><Translate>Committed</Translate></th>
+ <th>
+ <Translate>Created at</Translate>
+ </th>
+ <th>
+ <Translate>Expires at</Translate>
+ </th>
+ <th>
+ <Translate>Initial</Translate>
+ </th>
+ <th>
+ <Translate>Picked up</Translate>
+ </th>
+ <th>
+ <Translate>Committed</Translate>
+ </th>
<th />
</tr>
</thead>
<tbody>
- {instances.map(i => {
- return <tr key={i.id}>
- <td onClick={(): void => onSelect(i)} style={{ cursor: 'pointer'
}} >{i.creation_time.t_ms === "never" ? "never" : format(i.creation_time.t_ms,
'yyyy/MM/dd HH:mm:ss')}</td>
- <td onClick={(): void => onSelect(i)} style={{ cursor: 'pointer'
}} >{i.expiration_time.t_ms === "never" ? "never" :
format(i.expiration_time.t_ms, 'yyyy/MM/dd HH:mm:ss')}</td>
- <td onClick={(): void => onSelect(i)} style={{ cursor: 'pointer'
}} >{i.exchange_initial_amount}</td>
- <td onClick={(): void => onSelect(i)} style={{ cursor: 'pointer'
}} >{i.pickup_amount}</td>
- <td onClick={(): void => onSelect(i)} style={{ cursor: 'pointer'
}} >{i.committed_amount}</td>
- <td class="is-actions-cell right-sticky">
- <div class="buttons is-right">
- <button class="button is-small is-danger has-tooltip-left"
- data-tooltip={i18n`delete selected reserve from the
database`}
- type="button" onClick={(): void => onDelete(i)}>
- Delete
- </button>
- <button class="button is-small is-info has-tooltip-left"
- data-tooltip={i18n`authorize new tip from selected
reserve`}
- type="button" onClick={(): void => onNewTip(i)}>
- New Tip
- </button>
- </div>
- </td>
- </tr>
+ {instances.map((i) => {
+ return (
+ <tr key={i.id}>
+ <td
+ onClick={(): void => onSelect(i)}
+ style={{ cursor: "pointer" }}
+ >
+ {i.creation_time.t_s === "never"
+ ? "never"
+ : format(i.creation_time.t_s, "yyyy/MM/dd HH:mm:ss")}
+ </td>
+ <td
+ onClick={(): void => onSelect(i)}
+ style={{ cursor: "pointer" }}
+ >
+ {i.expiration_time.t_s === "never"
+ ? "never"
+ : format(i.expiration_time.t_s, "yyyy/MM/dd HH:mm:ss")}
+ </td>
+ <td
+ onClick={(): void => onSelect(i)}
+ style={{ cursor: "pointer" }}
+ >
+ {i.exchange_initial_amount}
+ </td>
+ <td
+ onClick={(): void => onSelect(i)}
+ style={{ cursor: "pointer" }}
+ >
+ {i.pickup_amount}
+ </td>
+ <td
+ onClick={(): void => onSelect(i)}
+ style={{ cursor: "pointer" }}
+ >
+ {i.committed_amount}
+ </td>
+ <td class="is-actions-cell right-sticky">
+ <div class="buttons is-right">
+ <button
+ class="button is-small is-danger has-tooltip-left"
+ data-tooltip={i18n`delete selected reserve from the
database`}
+ type="button"
+ onClick={(): void => onDelete(i)}
+ >
+ Delete
+ </button>
+ <button
+ class="button is-small is-info has-tooltip-left"
+ data-tooltip={i18n`authorize new tip from selected
reserve`}
+ type="button"
+ onClick={(): void => onNewTip(i)}
+ >
+ New Tip
+ </button>
+ </div>
+ </td>
+ </tr>
+ );
})}
-
</tbody>
- </table></div>)
+ </table>
+ </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>There is no ready reserves yet, add more pressing the + sign
or fund them</Translate></p>
- </div>
+ 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>
+ There is no ready reserves yet, add more pressing the + sign or fund
+ them
+ </Translate>
+ </p>
+ </div>
+ );
}
-function TableWithoutFund({ instances, onSelect, onDelete }: TableProps):
VNode {
- const i18n = useTranslator()
+function TableWithoutFund({
+ instances,
+ onSelect,
+ onDelete,
+}: TableProps): VNode {
+ const i18n = useTranslator();
return (
<div class="table-container">
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
<thead>
<tr>
- <th><Translate>Created at</Translate></th>
- <th><Translate>Expires at</Translate></th>
- <th><Translate>Expected Balance</Translate></th>
+ <th>
+ <Translate>Created at</Translate>
+ </th>
+ <th>
+ <Translate>Expires at</Translate>
+ </th>
+ <th>
+ <Translate>Expected Balance</Translate>
+ </th>
<th />
</tr>
</thead>
<tbody>
- {instances.map(i => {
- return <tr key={i.id}>
- <td onClick={(): void => onSelect(i)} style={{ cursor: 'pointer'
}} >{i.creation_time.t_ms === "never" ? "never" : format(i.creation_time.t_ms,
'yyyy/MM/dd HH:mm:ss')}</td>
- <td onClick={(): void => onSelect(i)} style={{ cursor: 'pointer'
}} >{i.expiration_time.t_ms === "never" ? "never" :
format(i.expiration_time.t_ms, 'yyyy/MM/dd HH:mm:ss')}</td>
- <td onClick={(): void => onSelect(i)} style={{ cursor: 'pointer'
}} >{i.merchant_initial_amount}</td>
- <td class="is-actions-cell right-sticky">
- <div class="buttons is-right">
- <button class="button is-small is-danger jb-modal
has-tooltip-left" type="button"
- data-tooltip={i18n`delete selected reserve from the
database`}
- onClick={(): void => onDelete(i)}>
- Delete
- </button>
- </div>
- </td>
- </tr>
+ {instances.map((i) => {
+ return (
+ <tr key={i.id}>
+ <td
+ onClick={(): void => onSelect(i)}
+ style={{ cursor: "pointer" }}
+ >
+ {i.creation_time.t_s === "never"
+ ? "never"
+ : format(i.creation_time.t_s, "yyyy/MM/dd HH:mm:ss")}
+ </td>
+ <td
+ onClick={(): void => onSelect(i)}
+ style={{ cursor: "pointer" }}
+ >
+ {i.expiration_time.t_s === "never"
+ ? "never"
+ : format(i.expiration_time.t_s, "yyyy/MM/dd HH:mm:ss")}
+ </td>
+ <td
+ onClick={(): void => onSelect(i)}
+ style={{ cursor: "pointer" }}
+ >
+ {i.merchant_initial_amount}
+ </td>
+ <td class="is-actions-cell right-sticky">
+ <div class="buttons is-right">
+ <button
+ class="button is-small is-danger jb-modal
has-tooltip-left"
+ type="button"
+ data-tooltip={i18n`delete selected reserve from the
database`}
+ onClick={(): void => onDelete(i)}
+ >
+ Delete
+ </button>
+ </div>
+ </td>
+ </tr>
+ );
})}
-
</tbody>
- </table></div>)
+ </table>
+ </div>
+ );
}
diff --git
a/packages/merchant-backoffice/src/paths/instance/transfers/list/List.stories.tsx
b/packages/merchant-backoffice/src/paths/instance/transfers/list/List.stories.tsx
index 401e666..ac29e3d 100644
---
a/packages/merchant-backoffice/src/paths/instance/transfers/list/List.stories.tsx
+++
b/packages/merchant-backoffice/src/paths/instance/transfers/list/List.stories.tsx
@@ -15,73 +15,79 @@
*/
/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-
-import { h, VNode, FunctionalComponent } from 'preact';
-import { ListPage as TestedComponent } from './ListPage';
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+import { h, VNode, FunctionalComponent } from "preact";
+import { ListPage as TestedComponent } from "./ListPage";
export default {
- title: 'Pages/Transfer/List',
+ title: "Pages/Transfer/List",
component: TestedComponent,
argTypes: {
- onCreate: { action: 'onCreate' },
- onDelete: { action: 'onDelete' },
- onLoadMoreBefore: { action: 'onLoadMoreBefore' },
- onLoadMoreAfter: { action: 'onLoadMoreAfter' },
- onShowAll: { action: 'onShowAll' },
- onShowVerified: { action: 'onShowVerified' },
- onShowUnverified: { action: 'onShowUnverified' },
- onChangePayTo: { action: 'onChangePayTo' }
+ onCreate: { action: "onCreate" },
+ onDelete: { action: "onDelete" },
+ onLoadMoreBefore: { action: "onLoadMoreBefore" },
+ onLoadMoreAfter: { action: "onLoadMoreAfter" },
+ onShowAll: { action: "onShowAll" },
+ onShowVerified: { action: "onShowVerified" },
+ onShowUnverified: { action: "onShowUnverified" },
+ onChangePayTo: { action: "onChangePayTo" },
},
};
-function createExample<Props>(Component: FunctionalComponent<Props>, props:
Partial<Props>) {
- const r = (args: any) => <Component {...args} />
- r.args = props
- return r
+function createExample<Props>(
+ Component: FunctionalComponent<Props>,
+ props: Partial<Props>
+) {
+ const r = (args: any) => <Component {...args} />;
+ r.args = props;
+ return r;
}
export const Example = createExample(TestedComponent, {
- transfers: [{
- exchange_url: 'http://exchange.url/',
- credit_amount: 'TESTKUDOS:10',
- payto_uri: 'payto//x-taler-bank/bank:8080/account',
- transfer_serial_id: 123123123,
- wtid: '!@KJELQKWEJ!L@K#!J@',
- confirmed: true,
- execution_time: {
- t_ms: new Date().getTime()
+ transfers: [
+ {
+ exchange_url: "http://exchange.url/",
+ credit_amount: "TESTKUDOS:10",
+ payto_uri: "payto//x-taler-bank/bank:8080/account",
+ transfer_serial_id: 123123123,
+ wtid: "!@KJELQKWEJ!L@K#!J@",
+ confirmed: true,
+ execution_time: {
+ t_s: new Date().getTime(),
+ },
+ verified: false,
},
- verified: false,
- }, {
- exchange_url: 'http://exchange.url/',
- credit_amount: 'TESTKUDOS:10',
- payto_uri: 'payto//x-taler-bank/bank:8080/account',
- transfer_serial_id: 123123123,
- wtid: '!@KJELQKWEJ!L@K#!J@',
- confirmed: true,
- execution_time: {
- t_ms: new Date().getTime()
+ {
+ exchange_url: "http://exchange.url/",
+ credit_amount: "TESTKUDOS:10",
+ payto_uri: "payto//x-taler-bank/bank:8080/account",
+ transfer_serial_id: 123123123,
+ wtid: "!@KJELQKWEJ!L@K#!J@",
+ confirmed: true,
+ execution_time: {
+ t_s: new Date().getTime(),
+ },
+ verified: false,
},
- verified: false,
- }, {
- exchange_url: 'http://exchange.url/',
- credit_amount: 'TESTKUDOS:10',
- payto_uri: 'payto//x-taler-bank/bank:8080/account',
- transfer_serial_id: 123123123,
- wtid: '!@KJELQKWEJ!L@K#!J@',
- confirmed: true,
- execution_time: {
- t_ms: new Date().getTime()
+ {
+ exchange_url: "http://exchange.url/",
+ credit_amount: "TESTKUDOS:10",
+ payto_uri: "payto//x-taler-bank/bank:8080/account",
+ transfer_serial_id: 123123123,
+ wtid: "!@KJELQKWEJ!L@K#!J@",
+ confirmed: true,
+ execution_time: {
+ t_s: new Date().getTime(),
+ },
+ verified: false,
},
- verified: false,
- }],
- accounts: ['payto://x-taler-bank/bank/some_account']
+ ],
+ accounts: ["payto://x-taler-bank/bank/some_account"],
});
export const Empty = createExample(TestedComponent, {
transfers: [],
- accounts: []
+ accounts: [],
});
diff --git
a/packages/merchant-backoffice/src/paths/instance/transfers/list/Table.tsx
b/packages/merchant-backoffice/src/paths/instance/transfers/list/Table.tsx
index 96c6d8c..27ad06a 100644
--- a/packages/merchant-backoffice/src/paths/instance/transfers/list/Table.tsx
+++ b/packages/merchant-backoffice/src/paths/instance/transfers/list/Table.tsx
@@ -15,17 +15,17 @@
*/
/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
-import { format } from "date-fns"
-import { h, VNode } from "preact"
-import { StateUpdater, useState } from "preact/hooks"
-import { MerchantBackend, WithId } from "../../../../declaration"
-import { Translate, useTranslator } from "../../../../i18n"
+import { format } from "date-fns";
+import { h, VNode } from "preact";
+import { StateUpdater, useState } from "preact/hooks";
+import { MerchantBackend, WithId } from "../../../../declaration";
+import { Translate, useTranslator } from "../../../../i18n";
-type Entity = MerchantBackend.Transfers.TransferDetails & WithId
+type Entity = MerchantBackend.Transfers.TransferDetails & WithId;
interface Props {
transfers: Entity[];
@@ -38,39 +38,60 @@ interface Props {
onLoadMoreAfter?: () => void;
}
-export function CardTable({ transfers, onCreate, onDelete, onLoadMoreAfter,
onLoadMoreBefore, hasMoreAfter, hasMoreBefore }: Props): VNode {
- const [rowSelection, rowSelectionHandler] = useState<string[]>([])
-
- const i18n = useTranslator()
+export function CardTable({
+ transfers,
+ onCreate,
+ onDelete,
+ onLoadMoreAfter,
+ onLoadMoreBefore,
+ hasMoreAfter,
+ hasMoreBefore,
+}: Props): VNode {
+ const [rowSelection, rowSelectionHandler] = useState<string[]>([]);
- return <div class="card has-table">
- <header class="card-header">
- <p class="card-header-title"><span class="icon"><i class="mdi mdi-bank"
/></span><Translate>Transfers</Translate></p>
- <div class="card-header-icon" aria-label="more options">
- <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>
+ const i18n = useTranslator();
- </header>
- <div class="card-content">
- <div class="b-table has-pagination">
- <div class="table-wrapper has-mobile-cards">
- {transfers.length > 0 ?
- <Table instances={transfers}
- onDelete={onDelete} rowSelection={rowSelection}
- rowSelectionHandler={rowSelectionHandler}
- onLoadMoreAfter={onLoadMoreAfter}
onLoadMoreBefore={onLoadMoreBefore}
- hasMoreAfter={hasMoreAfter} hasMoreBefore={hasMoreBefore}
- /> :
- <EmptyTable />
- }
+ return (
+ <div class="card has-table">
+ <header class="card-header">
+ <p class="card-header-title">
+ <span class="icon">
+ <i class="mdi mdi-bank" />
+ </span>
+ <Translate>Transfers</Translate>
+ </p>
+ <div class="card-header-icon" aria-label="more options">
+ <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>
+ <div class="card-content">
+ <div class="b-table has-pagination">
+ <div class="table-wrapper has-mobile-cards">
+ {transfers.length > 0 ? (
+ <Table
+ instances={transfers}
+ onDelete={onDelete}
+ rowSelection={rowSelection}
+ rowSelectionHandler={rowSelectionHandler}
+ onLoadMoreAfter={onLoadMoreAfter}
+ onLoadMoreBefore={onLoadMoreBefore}
+ hasMoreAfter={hasMoreAfter}
+ hasMoreBefore={hasMoreBefore}
+ />
+ ) : (
+ <EmptyTable />
+ )}
+ </div>
</div>
</div>
</div>
- </div>
+ );
}
interface TableProps {
rowSelection: string[];
@@ -84,59 +105,118 @@ interface TableProps {
}
function toggleSelected<T>(id: T): (prev: T[]) => T[] {
- return (prev: T[]): T[] => prev.indexOf(id) == -1 ? [...prev, id] :
prev.filter(e => e != id)
+ return (prev: T[]): T[] =>
+ prev.indexOf(id) == -1 ? [...prev, id] : prev.filter((e) => e != id);
}
-function Table({ instances, onLoadMoreAfter, onDelete, onLoadMoreBefore,
hasMoreAfter, hasMoreBefore }: TableProps): VNode {
- const i18n = useTranslator()
+function Table({
+ instances,
+ onLoadMoreAfter,
+ onDelete,
+ onLoadMoreBefore,
+ hasMoreAfter,
+ hasMoreBefore,
+}: TableProps): VNode {
+ const i18n = useTranslator();
return (
<div class="table-container">
- {onLoadMoreBefore && <button class="button is-fullwidth"
- data-tooltip={i18n`load more transfers before the first one`}
- disabled={!hasMoreBefore} onClick={onLoadMoreBefore}><Translate>load
newer transfers</Translate></button>}
+ {onLoadMoreBefore && (
+ <button
+ class="button is-fullwidth"
+ data-tooltip={i18n`load more transfers before the first one`}
+ disabled={!hasMoreBefore}
+ onClick={onLoadMoreBefore}
+ >
+ <Translate>load newer transfers</Translate>
+ </button>
+ )}
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
<thead>
<tr>
- <th><Translate>ID</Translate></th>
- <th><Translate>Credit</Translate></th>
- <th><Translate>Address</Translate></th>
- <th><Translate>Exchange URL</Translate></th>
- <th><Translate>Confirmed</Translate></th>
- <th><Translate>Verified</Translate></th>
- <th><Translate>Executed at</Translate></th>
+ <th>
+ <Translate>ID</Translate>
+ </th>
+ <th>
+ <Translate>Credit</Translate>
+ </th>
+ <th>
+ <Translate>Address</Translate>
+ </th>
+ <th>
+ <Translate>Exchange URL</Translate>
+ </th>
+ <th>
+ <Translate>Confirmed</Translate>
+ </th>
+ <th>
+ <Translate>Verified</Translate>
+ </th>
+ <th>
+ <Translate>Executed at</Translate>
+ </th>
<th />
</tr>
</thead>
<tbody>
- {instances.map(i => {
- return <tr key={i.id}>
- <td>{i.id}</td>
- <td>{i.credit_amount}</td>
- <td>{i.payto_uri}</td>
- <td>{i.exchange_url}</td>
- <td>{i.confirmed ? i18n`yes` : i18n`no`}</td>
- <td>{i.verified ? i18n`yes` : i18n`no`}</td>
- <td>{i.execution_time ? (i.execution_time.t_ms == 'never' ?
i18n`never` : format(i.execution_time.t_ms, 'yyyy/MM/dd HH:mm:ss')) :
i18n`unknown`}</td>
- <td>
- {i.verified === undefined ? <button class="button is-danger
is-small has-tooltip-left"
- data-tooltip={i18n`delete selected transfer from the database`}
- onClick={() => onDelete(i)}>Delete</button> : undefined}
- </td>
- </tr>
+ {instances.map((i) => {
+ return (
+ <tr key={i.id}>
+ <td>{i.id}</td>
+ <td>{i.credit_amount}</td>
+ <td>{i.payto_uri}</td>
+ <td>{i.exchange_url}</td>
+ <td>{i.confirmed ? i18n`yes` : i18n`no`}</td>
+ <td>{i.verified ? i18n`yes` : i18n`no`}</td>
+ <td>
+ {i.execution_time
+ ? i.execution_time.t_s == "never"
+ ? i18n`never`
+ : format(i.execution_time.t_s, "yyyy/MM/dd HH:mm:ss")
+ : i18n`unknown`}
+ </td>
+ <td>
+ {i.verified === undefined ? (
+ <button
+ class="button is-danger is-small has-tooltip-left"
+ data-tooltip={i18n`delete selected transfer from the
database`}
+ onClick={() => onDelete(i)}
+ >
+ Delete
+ </button>
+ ) : undefined}
+ </td>
+ </tr>
+ );
})}
</tbody>
</table>
- {onLoadMoreAfter && <button class="button is-fullwidth"
data-tooltip={i18n`load more transfer after the last one`}
disabled={!hasMoreAfter} onClick={onLoadMoreAfter}><Translate>load older
transfers</Translate></button>}
- </div>)
+ {onLoadMoreAfter && (
+ <button
+ class="button is-fullwidth"
+ data-tooltip={i18n`load more transfer after the last one`}
+ disabled={!hasMoreAfter}
+ onClick={onLoadMoreAfter}
+ >
+ <Translate>load older transfers</Translate>
+ </button>
+ )}
+ </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>There is no transfer yet, add more pressing the +
sign</Translate></p>
- </div>
+ 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>
+ There is no transfer yet, add more pressing the + sign
+ </Translate>
+ </p>
+ </div>
+ );
}
-
-
diff --git
a/packages/merchant-backoffice/src/paths/instance/update/Update.stories.tsx
b/packages/merchant-backoffice/src/paths/instance/update/Update.stories.tsx
index d0c18dd..3239d9c 100644
--- a/packages/merchant-backoffice/src/paths/instance/update/Update.stories.tsx
+++ b/packages/merchant-backoffice/src/paths/instance/update/Update.stories.tsx
@@ -15,45 +15,47 @@
*/
/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-
-import { h, VNode, FunctionalComponent } from 'preact';
-import { UpdatePage as TestedComponent } from './UpdatePage';
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+import { h, VNode, FunctionalComponent } from "preact";
+import { UpdatePage as TestedComponent } from "./UpdatePage";
export default {
- title: 'Pages/Instance/Update',
+ title: "Pages/Instance/Update",
component: TestedComponent,
argTypes: {
- onUpdate: { action: 'onUpdate' },
- onBack: { action: 'onBack' },
+ onUpdate: { action: "onUpdate" },
+ onBack: { action: "onBack" },
},
};
-function createExample<Props>(Component: FunctionalComponent<Props>, props:
Partial<Props>) {
- const r = (args: any) => <Component {...args} />
- r.args = props
- return r
+function createExample<Props>(
+ Component: FunctionalComponent<Props>,
+ props: Partial<Props>
+) {
+ const r = (args: any) => <Component {...args} />;
+ r.args = props;
+ return r;
}
export const Example = createExample(TestedComponent, {
selected: {
accounts: [],
- name: 'name',
- auth: {method:'external'},
+ name: "name",
+ auth: { method: "external" },
address: {},
jurisdiction: {},
- default_max_deposit_fee: 'TESTKUDOS:2',
- default_max_wire_fee: 'TESTKUDOS:1',
+ default_max_deposit_fee: "TESTKUDOS:2",
+ default_max_wire_fee: "TESTKUDOS:1",
default_pay_delay: {
- d_ms: 1000000,
+ d_us: 1000000,
},
default_wire_fee_amortization: 1,
default_wire_transfer_delay: {
- d_ms: 100000,
+ d_us: 100000,
},
- merchant_pub: 'ASDWQEKASJDKSADJ'
- }
+ merchant_pub: "ASDWQEKASJDKSADJ",
+ },
});
diff --git
a/packages/merchant-backoffice/src/paths/instance/update/UpdatePage.tsx
b/packages/merchant-backoffice/src/paths/instance/update/UpdatePage.tsx
index 741edc6..7660520 100644
--- a/packages/merchant-backoffice/src/paths/instance/update/UpdatePage.tsx
+++ b/packages/merchant-backoffice/src/paths/instance/update/UpdatePage.tsx
@@ -58,8 +58,8 @@ function convert(
const payto_uris = accounts.filter((a) => a.active).map((a) => a.payto_uri);
const defaults = {
default_wire_fee_amortization: 1,
- default_pay_delay: { d_ms: 1000 * 60 * 60 }, //one hour
- default_wire_transfer_delay: { d_ms: 1000 * 60 * 60 * 2 }, //two hours
+ default_pay_delay: { d_us: 1000 * 60 * 60 }, //one hour
+ default_wire_transfer_delay: { d_us: 1000 * 60 * 60 * 2 }, //two hours
};
return { ...defaults, ...rest, payto_uris };
}
diff --git a/packages/merchant-backoffice/src/schemas/index.ts
b/packages/merchant-backoffice/src/schemas/index.ts
index 9d64a67..4223596 100644
--- a/packages/merchant-backoffice/src/schemas/index.ts
+++ b/packages/merchant-backoffice/src/schemas/index.ts
@@ -99,12 +99,12 @@ export const InstanceSchema = yup.object().shape({
country_subdivision: yup.string().optional(),
}).meta({ type: 'group' }),
// default_pay_delay: yup.object()
- // .shape({ d_ms: yup.number() })
+ // .shape({ d_us: yup.number() })
// .required()
// .meta({ type: 'duration' }),
// .transform(numberToDuration),
default_wire_transfer_delay: yup.object()
- .shape({ d_ms: yup.number() })
+ .shape({ d_us: yup.number() })
.required()
.meta({ type: 'duration' }),
// .transform(numberToDuration),
diff --git a/packages/merchant-backoffice/src/utils/amount.ts
b/packages/merchant-backoffice/src/utils/amount.ts
index 062ddaf..85f2304 100644
--- a/packages/merchant-backoffice/src/utils/amount.ts
+++ b/packages/merchant-backoffice/src/utils/amount.ts
@@ -38,10 +38,10 @@ export function mergeRefunds(prev:
MerchantBackend.Orders.RefundDetails[], cur:
let tail;
if (prev.length === 0 || //empty list
- cur.timestamp.t_ms === 'never' || //current doesnt have timestamp
- (tail = prev[prev.length - 1]).timestamp.t_ms === 'never' || // last
doesnt have timestamp
+ cur.timestamp.t_s === 'never' || //current doesnt have timestamp
+ (tail = prev[prev.length - 1]).timestamp.t_s === 'never' || // last doesnt
have timestamp
cur.reason !== tail.reason || //different reason
- Math.abs(cur.timestamp.t_ms - tail.timestamp.t_ms) > 1000 * 60) {//more
than 1 minute difference
+ Math.abs(cur.timestamp.t_s - tail.timestamp.t_s) > 1000 * 60) {//more than
1 minute difference
prev.push(cur)
return prev
diff --git a/packages/merchant-backoffice/tests/hooks/swr/reserve.test.ts
b/packages/merchant-backoffice/tests/hooks/swr/reserve.test.ts
index c6c8818..0361c54 100644
--- a/packages/merchant-backoffice/tests/hooks/swr/reserve.test.ts
+++ b/packages/merchant-backoffice/tests/hooks/swr/reserve.test.ts
@@ -288,7 +288,7 @@ describe("reserve api interaction with details", () => {
response: {
tip_id: "id2",
taler_tip_uri: "uri",
- tip_expiration: { t_ms: 1 },
+ tip_expiration: { t_s: 1 },
tip_status_url: "url",
},
});
@@ -381,7 +381,7 @@ describe("reserve api interaction with details", () => {
response: {
tip_id: "id2",
taler_tip_uri: "uri",
- tip_expiration: { t_ms: 1 },
+ tip_expiration: { t_s: 1 },
tip_status_url: "url",
},
});
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.