gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] 06/08: show simple order creation unless advance mod


From: gnunet
Subject: [taler-wallet-core] 06/08: show simple order creation unless advance mode is selected
Date: Mon, 07 Aug 2023 13:14:56 +0200

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a commit to branch master
in repository wallet-core.

commit 7d53aa2755386ef43d3cb17032b502c9faf2b4e7
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Mon Aug 7 08:13:29 2023 -0300

    show simple order creation unless advance mode is selected
---
 .../src/components/form/InputToggle.tsx            |  91 ++++++
 packages/merchant-backoffice-ui/src/hooks/index.ts |  10 +-
 .../src/hooks/useSettings.ts                       |  59 ++++
 .../paths/instance/orders/create/CreatePage.tsx    | 309 +++++++++++----------
 4 files changed, 315 insertions(+), 154 deletions(-)

diff --git 
a/packages/merchant-backoffice-ui/src/components/form/InputToggle.tsx 
b/packages/merchant-backoffice-ui/src/components/form/InputToggle.tsx
new file mode 100644
index 000000000..61ddf3c84
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/components/form/InputToggle.tsx
@@ -0,0 +1,91 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+import { h, VNode } from "preact";
+import { InputProps, useField } from "./useField.js";
+
+interface Props<T> extends InputProps<T> {
+  name: T;
+  readonly?: boolean;
+  expand?: boolean;
+  threeState?: boolean;
+  toBoolean?: (v?: any) => boolean | undefined;
+  fromBoolean?: (s: boolean | undefined) => any;
+}
+
+const defaultToBoolean = (f?: any): boolean | undefined => f || "";
+const defaultFromBoolean = (v: boolean | undefined): any => v as any;
+
+export function InputToggle<T>({
+  name,
+  readonly,
+  placeholder,
+  tooltip,
+  label,
+  help,
+  threeState,
+  expand,
+  fromBoolean = defaultFromBoolean,
+  toBoolean = defaultToBoolean,
+}: Props<keyof T>): VNode {
+  const { error, value, onChange } = useField<T>(name);
+
+  const onCheckboxClick = (): void => {
+    const c = toBoolean(value);
+    if (c === false && threeState) return onChange(undefined as any);
+    return onChange(fromBoolean(!c));
+  };
+
+  return (
+    <div class="field is-horizontal">
+      <div class="field-label is-normal">
+        <label class="label" style={{ width: 200 }}>
+          {label}
+          {tooltip && (
+            <span class="icon has-tooltip-right" data-tooltip={tooltip}>
+              <i class="mdi mdi-information" />
+            </span>
+          )}
+        </label>
+      </div>
+      <div class="field-body is-flex-grow-1">
+        <div class="field">
+          <p class={expand ? "control is-expanded" : "control"}>
+            <label class="toggle" style={{ marginLeft: 4, marginTop: 0 }}>
+              <input
+                type="checkbox"
+                class={toBoolean(value) === undefined ? "is-indeterminate" : 
"toggle-checkbox"}
+                checked={toBoolean(value)}
+                placeholder={placeholder}
+                readonly={readonly}
+                name={String(name)}
+                disabled={readonly}
+                onChange={onCheckboxClick}
+              />
+              <div class="toggle-switch"></div>
+            </label>
+            {help}
+          </p>
+          {error && <p class="help is-danger">{error}</p>}
+        </div>
+      </div>
+    </div>
+  );
+}
diff --git a/packages/merchant-backoffice-ui/src/hooks/index.ts 
b/packages/merchant-backoffice-ui/src/hooks/index.ts
index 10e77716e..b77b9dea8 100644
--- a/packages/merchant-backoffice-ui/src/hooks/index.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/index.ts
@@ -53,15 +53,17 @@ export function useBackendURL(
 
 export function useBackendDefaultToken(
   initialValue?: string,
-): [string | undefined, ((d:string | undefined) => void)] {
-  const {update, value} = useMemoryStorage(`backend-token`, initialValue)
+): [string | undefined, ((d: string | undefined) => void)] {
+  // uncomment for testing
+  initialValue = "secret-token:secret" as string | undefined
+  const { update, value } = useMemoryStorage(`backend-token`, initialValue)
   return [value, update];
 }
 
 export function useBackendInstanceToken(
   id: string,
-): [string | undefined, ((d:string | undefined) => void)] {
-  const {update:setToken, value:token, reset} = 
useMemoryStorage(`backend-token-${id}`)
+): [string | undefined, ((d: string | undefined) => void)] {
+  const { update: setToken, value: token, reset } = 
useMemoryStorage(`backend-token-${id}`)
   const [defaultToken, defaultSetToken] = useBackendDefaultToken();
 
   // instance named 'default' use the default token
diff --git a/packages/merchant-backoffice-ui/src/hooks/useSettings.ts 
b/packages/merchant-backoffice-ui/src/hooks/useSettings.ts
new file mode 100644
index 000000000..5c0932f27
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/hooks/useSettings.ts
@@ -0,0 +1,59 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
+import {
+  Codec,
+  buildCodecForObject,
+  codecForBoolean,
+} from "@gnu-taler/taler-util";
+
+function parse_json_or_undefined<T>(str: string | undefined): T | undefined {
+  if (str === undefined) return undefined;
+  try {
+    return JSON.parse(str);
+  } catch {
+    return undefined;
+  }
+}
+
+export interface Settings {
+  advanceOrderMode: boolean
+}
+
+const defaultSettings: Settings = {
+  advanceOrderMode: false,
+}
+
+export const codecForSettings = (): Codec<Settings> =>
+  buildCodecForObject<Settings>()
+    .property("advanceOrderMode", codecForBoolean())
+    .build("Settings");
+
+const SETTINGS_KEY = buildStorageKey("merchant-settings", codecForSettings());
+
+export function useSettings(): [
+  Readonly<Settings>,
+  <T extends keyof Settings>(key: T, value: Settings[T]) => void,
+] {
+  const { value, update } = useLocalStorage(SETTINGS_KEY);
+
+  const parsed: Settings = value ?? defaultSettings;
+  function updateField<T extends keyof Settings>(k: T, v: Settings[T]) {
+    update({ ...parsed, [k]: v });
+  }
+  return [parsed, updateField];
+}
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
index c8cc20ae0..fa9347c6e 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
@@ -43,6 +43,7 @@ import { Duration, MerchantBackend, WithId } from 
"../../../../declaration.js";
 import { OrderCreateSchema as schema } from "../../../../schemas/index.js";
 import { rate } from "../../../../utils/amount.js";
 import { undefinedIfEmpty } from "../../../../utils/table.js";
+import { useSettings } from "../../../../hooks/useSettings.js";
 
 interface Props {
   onCreate: (d: MerchantBackend.Orders.PostOrderRequest) => void;
@@ -62,8 +63,8 @@ function with_defaults(config: InstanceConfig): 
Partial<Entity> {
     !config.default_pay_delay || config.default_pay_delay.d_us === "forever"
       ? undefined
       : add(new Date(), {
-          seconds: config.default_pay_delay.d_us / (1000 * 1000),
-        });
+        seconds: config.default_pay_delay.d_us / (1000 * 1000),
+      });
 
   return {
     inventoryProducts: {},
@@ -138,7 +139,7 @@ export function CreatePage({
   const [value, valueHandler] = useState(with_defaults(instanceConfig));
   const config = useConfigContext();
   const zero = Amounts.zeroOfCurrency(config.currency);
-
+  const [settings] = useSettings()
   const inventoryList = Object.values(value.inventoryProducts || {});
   const productList = Object.values(value.products || {});
 
@@ -154,10 +155,10 @@ export function CreatePage({
       order_price: !value.pricing?.order_price
         ? i18n.str`required`
         : !parsedPrice
-        ? i18n.str`not valid`
-        : Amounts.isZero(parsedPrice)
-        ? i18n.str`must be greater than 0`
-        : undefined,
+          ? i18n.str`not valid`
+          : Amounts.isZero(parsedPrice)
+            ? i18n.str`must be greater than 0`
+            : undefined,
     }),
     extra:
       value.extra && !stringIsValidJSON(value.extra)
@@ -167,47 +168,47 @@ export function CreatePage({
       refund_deadline: !value.payments?.refund_deadline
         ? undefined
         : !isFuture(value.payments.refund_deadline)
-        ? i18n.str`should be in the future`
-        : value.payments.pay_deadline &&
-          isBefore(value.payments.refund_deadline, value.payments.pay_deadline)
-        ? i18n.str`refund deadline cannot be before pay deadline`
-        : value.payments.wire_transfer_deadline &&
-          isBefore(
-            value.payments.wire_transfer_deadline,
-            value.payments.refund_deadline,
-          )
-        ? i18n.str`wire transfer deadline cannot be before refund deadline`
-        : undefined,
+          ? i18n.str`should be in the future`
+          : value.payments.pay_deadline &&
+            isBefore(value.payments.refund_deadline, 
value.payments.pay_deadline)
+            ? i18n.str`refund deadline cannot be before pay deadline`
+            : value.payments.wire_transfer_deadline &&
+              isBefore(
+                value.payments.wire_transfer_deadline,
+                value.payments.refund_deadline,
+              )
+              ? i18n.str`wire transfer deadline cannot be before refund 
deadline`
+              : undefined,
       pay_deadline: !value.payments?.pay_deadline
         ? undefined
         : !isFuture(value.payments.pay_deadline)
-        ? i18n.str`should be in the future`
-        : value.payments.wire_transfer_deadline &&
-          isBefore(
-            value.payments.wire_transfer_deadline,
-            value.payments.pay_deadline,
-          )
-        ? i18n.str`wire transfer deadline cannot be before pay deadline`
-        : undefined,
+          ? i18n.str`should be in the future`
+          : value.payments.wire_transfer_deadline &&
+            isBefore(
+              value.payments.wire_transfer_deadline,
+              value.payments.pay_deadline,
+            )
+            ? i18n.str`wire transfer deadline cannot be before pay deadline`
+            : undefined,
       auto_refund_deadline: !value.payments?.auto_refund_deadline
         ? undefined
         : !isFuture(value.payments.auto_refund_deadline)
-        ? i18n.str`should be in the future`
-        : !value.payments?.refund_deadline
-        ? i18n.str`should have a refund deadline`
-        : !isAfter(
-            value.payments.refund_deadline,
-            value.payments.auto_refund_deadline,
-          )
-        ? i18n.str`auto refund cannot be after refund deadline`
-        : undefined,
+          ? i18n.str`should be in the future`
+          : !value.payments?.refund_deadline
+            ? i18n.str`should have a refund deadline`
+            : !isAfter(
+              value.payments.refund_deadline,
+              value.payments.auto_refund_deadline,
+            )
+              ? i18n.str`auto refund cannot be after refund deadline`
+              : undefined,
     }),
     shipping: undefinedIfEmpty({
       delivery_date: !value.shipping?.delivery_date
         ? undefined
         : !isFuture(value.shipping.delivery_date)
-        ? i18n.str`should be in the future`
-        : undefined,
+          ? i18n.str`should be in the future`
+          : undefined,
     }),
   };
   const hasErrors = Object.keys(errors).some(
@@ -227,27 +228,27 @@ export function CreatePage({
         extra: value.extra,
         pay_deadline: value.payments.pay_deadline
           ? {
-              t_s: Math.floor(value.payments.pay_deadline.getTime() / 1000),
-            }
+            t_s: Math.floor(value.payments.pay_deadline.getTime() / 1000),
+          }
           : undefined,
         wire_transfer_deadline: value.payments.wire_transfer_deadline
           ? {
-              t_s: Math.floor(
-                value.payments.wire_transfer_deadline.getTime() / 1000,
-              ),
-            }
+            t_s: Math.floor(
+              value.payments.wire_transfer_deadline.getTime() / 1000,
+            ),
+          }
           : undefined,
         refund_deadline: value.payments.refund_deadline
           ? {
-              t_s: Math.floor(value.payments.refund_deadline.getTime() / 1000),
-            }
+            t_s: Math.floor(value.payments.refund_deadline.getTime() / 1000),
+          }
           : undefined,
         auto_refund: value.payments.auto_refund_deadline
           ? {
-              d_us: Math.floor(
-                value.payments.auto_refund_deadline.getTime() * 1000,
-              ),
-            }
+            d_us: Math.floor(
+              value.payments.auto_refund_deadline.getTime() * 1000,
+            ),
+          }
           : undefined,
         wire_fee_amortization: value.payments.wire_fee_amortization as number,
         max_fee: value.payments.max_fee as string,
@@ -374,13 +375,15 @@ export function CreatePage({
                 inventory={instanceInventory}
               />
 
-              <NonInventoryProductFrom
-                productToEdit={editingProduct}
-                onAddProduct={(p) => {
-                  setEditingProduct(undefined);
-                  return addNewProduct(p);
-                }}
-              />
+              {settings.advanceOrderMode &&
+                <NonInventoryProductFrom
+                  productToEdit={editingProduct}
+                  onAddProduct={(p) => {
+                    setEditingProduct(undefined);
+                    return addNewProduct(p);
+                  }}
+                />
+              }
 
               {allProducts.length > 0 && (
                 <ProductList
@@ -423,8 +426,8 @@ export function CreatePage({
                       discountOrRise > 0 &&
                       (discountOrRise < 1
                         ? `discount of %${Math.round(
-                            (1 - discountOrRise) * 100,
-                          )}`
+                          (1 - discountOrRise) * 100,
+                        )}`
                         : `rise of %${Math.round((discountOrRise - 1) * 100)}`)
                     }
                     tooltip={i18n.str`Amount to be paid by the customer`}
@@ -445,102 +448,108 @@ export function CreatePage({
                 tooltip={i18n.str`Title of the order to be shown to the 
customer`}
               />
 
-              <InputGroup
-                name="shipping"
-                label={i18n.str`Shipping and Fulfillment`}
-                initialActive
-              >
-                <InputDate
-                  name="shipping.delivery_date"
-                  label={i18n.str`Delivery date`}
-                  tooltip={i18n.str`Deadline for physical delivery assured by 
the merchant.`}
-                />
-                {value.shipping?.delivery_date && (
-                  <InputGroup
-                    name="shipping.delivery_location"
-                    label={i18n.str`Location`}
-                    tooltip={i18n.str`address where the products will be 
delivered`}
-                  >
-                    <InputLocation name="shipping.delivery_location" />
-                  </InputGroup>
-                )}
-                <Input
-                  name="shipping.fullfilment_url"
-                  label={i18n.str`Fulfillment URL`}
-                  tooltip={i18n.str`URL to which the user will be redirected 
after successful payment.`}
-                />
-              </InputGroup>
+              {settings.advanceOrderMode &&
+                <InputGroup
+                  name="shipping"
+                  label={i18n.str`Shipping and Fulfillment`}
+                  initialActive
+                >
+                  <InputDate
+                    name="shipping.delivery_date"
+                    label={i18n.str`Delivery date`}
+                    tooltip={i18n.str`Deadline for physical delivery assured 
by the merchant.`}
+                  />
+                  {value.shipping?.delivery_date && (
+                    <InputGroup
+                      name="shipping.delivery_location"
+                      label={i18n.str`Location`}
+                      tooltip={i18n.str`address where the products will be 
delivered`}
+                    >
+                      <InputLocation name="shipping.delivery_location" />
+                    </InputGroup>
+                  )}
+                  <Input
+                    name="shipping.fullfilment_url"
+                    label={i18n.str`Fulfillment URL`}
+                    tooltip={i18n.str`URL to which the user will be redirected 
after successful payment.`}
+                  />
+                </InputGroup>
+              }
 
-              <InputGroup
-                name="payments"
-                label={i18n.str`Taler payment options`}
-                tooltip={i18n.str`Override default Taler payment settings for 
this order`}
-              >
-                <InputDate
-                  name="payments.pay_deadline"
-                  label={i18n.str`Payment deadline`}
-                  tooltip={i18n.str`Deadline for the customer to pay for the 
offer before it expires. Inventory products will be reserved until this 
deadline.`}
-                />
-                <InputDate
-                  name="payments.refund_deadline"
-                  label={i18n.str`Refund deadline`}
-                  tooltip={i18n.str`Time until which the order can be refunded 
by the merchant.`}
-                />
-                <InputDate
-                  name="payments.wire_transfer_deadline"
-                  label={i18n.str`Wire transfer deadline`}
-                  tooltip={i18n.str`Deadline for the exchange to make the wire 
transfer.`}
-                />
-                <InputDate
-                  name="payments.auto_refund_deadline"
-                  label={i18n.str`Auto-refund deadline`}
-                  tooltip={i18n.str`Time until which the wallet will 
automatically check for refunds without user interaction.`}
-                />
+              {settings.advanceOrderMode &&
+                <InputGroup
+                  name="payments"
+                  label={i18n.str`Taler payment options`}
+                  tooltip={i18n.str`Override default Taler payment settings 
for this order`}
+                >
+                  <InputDate
+                    name="payments.pay_deadline"
+                    label={i18n.str`Payment deadline`}
+                    tooltip={i18n.str`Deadline for the customer to pay for the 
offer before it expires. Inventory products will be reserved until this 
deadline.`}
+                  />
+                  <InputDate
+                    name="payments.refund_deadline"
+                    label={i18n.str`Refund deadline`}
+                    tooltip={i18n.str`Time until which the order can be 
refunded by the merchant.`}
+                  />
+                  <InputDate
+                    name="payments.wire_transfer_deadline"
+                    label={i18n.str`Wire transfer deadline`}
+                    tooltip={i18n.str`Deadline for the exchange to make the 
wire transfer.`}
+                  />
+                  <InputDate
+                    name="payments.auto_refund_deadline"
+                    label={i18n.str`Auto-refund deadline`}
+                    tooltip={i18n.str`Time until which the wallet will 
automatically check for refunds without user interaction.`}
+                  />
 
-                <InputCurrency
-                  name="payments.max_fee"
-                  label={i18n.str`Maximum deposit fee`}
-                  tooltip={i18n.str`Maximum deposit fees the merchant is 
willing to cover for this order. Higher deposit fees must be covered in full by 
the consumer.`}
-                />
-                <InputCurrency
-                  name="payments.max_wire_fee"
-                  label={i18n.str`Maximum wire fee`}
-                  tooltip={i18n.str`Maximum aggregate wire fees the merchant 
is willing to cover for this order. Wire fees exceeding this amount are to be 
covered by the customers.`}
-                />
-                <InputNumber
-                  name="payments.wire_fee_amortization"
-                  label={i18n.str`Wire fee amortization`}
-                  tooltip={i18n.str`Factor by which wire fees exceeding the 
above threshold are divided to determine the share of excess wire fees to be 
paid explicitly by the consumer.`}
-                />
-                <InputBoolean
-                  name="payments.createToken"
-                  label={i18n.str`Create token`}
-                  tooltip={i18n.str`Uncheck this option if the merchant 
backend generated an order ID with enough entropy to prevent adversarial 
claims.`}
-                />
-                <InputNumber
-                  name="payments.minimum_age"
-                  label={i18n.str`Minimum age required`}
-                  tooltip={i18n.str`Any value greater than 0 will limit the 
coins able be used to pay this contract. If empty the age restriction will be 
defined by the products`}
-                  help={
-                    minAgeByProducts > 0
-                      ? i18n.str`Min age defined by the producs is 
${minAgeByProducts}`
-                      : undefined
-                  }
-                />
-              </InputGroup>
+                  <InputCurrency
+                    name="payments.max_fee"
+                    label={i18n.str`Maximum deposit fee`}
+                    tooltip={i18n.str`Maximum deposit fees the merchant is 
willing to cover for this order. Higher deposit fees must be covered in full by 
the consumer.`}
+                  />
+                  <InputCurrency
+                    name="payments.max_wire_fee"
+                    label={i18n.str`Maximum wire fee`}
+                    tooltip={i18n.str`Maximum aggregate wire fees the merchant 
is willing to cover for this order. Wire fees exceeding this amount are to be 
covered by the customers.`}
+                  />
+                  <InputNumber
+                    name="payments.wire_fee_amortization"
+                    label={i18n.str`Wire fee amortization`}
+                    tooltip={i18n.str`Factor by which wire fees exceeding the 
above threshold are divided to determine the share of excess wire fees to be 
paid explicitly by the consumer.`}
+                  />
+                  <InputBoolean
+                    name="payments.createToken"
+                    label={i18n.str`Create token`}
+                    tooltip={i18n.str`Uncheck this option if the merchant 
backend generated an order ID with enough entropy to prevent adversarial 
claims.`}
+                  />
+                  <InputNumber
+                    name="payments.minimum_age"
+                    label={i18n.str`Minimum age required`}
+                    tooltip={i18n.str`Any value greater than 0 will limit the 
coins able be used to pay this contract. If empty the age restriction will be 
defined by the products`}
+                    help={
+                      minAgeByProducts > 0
+                        ? i18n.str`Min age defined by the producs is 
${minAgeByProducts}`
+                        : undefined
+                    }
+                  />
+                </InputGroup>
+              }
 
-              <InputGroup
-                name="extra"
-                label={i18n.str`Additional information`}
-                tooltip={i18n.str`Custom information to be included in the 
contract for this order.`}
-              >
-                <Input
+              {settings.advanceOrderMode &&
+                <InputGroup
                   name="extra"
-                  inputType="multiline"
-                  label={`Value`}
-                  tooltip={i18n.str`You must enter a value in JavaScript 
Object Notation (JSON).`}
-                />
-              </InputGroup>
+                  label={i18n.str`Additional information`}
+                  tooltip={i18n.str`Custom information to be included in the 
contract for this order.`}
+                >
+                  <Input
+                    name="extra"
+                    inputType="multiline"
+                    label={`Value`}
+                    tooltip={i18n.str`You must enter a value in JavaScript 
Object Notation (JSON).`}
+                  />
+                </InputGroup>
+              }
             </FormProvider>
 
             <div class="buttons is-right mt-5">

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]