gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 01/02: fix templates


From: gnunet
Subject: [taler-wallet-core] 01/02: fix templates
Date: Thu, 18 Jan 2024 21:51:06 +0100

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

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

commit 421b5c0acfa310fcb7f1893bf81e38f2876e1cbf
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Thu Jan 18 16:16:21 2024 -0300

    fix templates
---
 .../merchant-backoffice-ui/src/Application.tsx     |   6 +-
 .../src/ApplicationReadyRoutes.tsx                 |   5 +-
 .../merchant-backoffice-ui/src/InstanceRoutes.tsx  |  20 +-
 .../src/components/form/InputDuration.tsx          |   3 +
 .../instance/otp_devices/create/CreatePage.tsx     |   9 +-
 .../paths/instance/templates/create/CreatePage.tsx | 190 ++++++++++++-------
 .../src/paths/instance/templates/list/index.tsx    |   2 +-
 .../paths/instance/templates/update/UpdatePage.tsx | 205 ++++++++++++++-------
 .../src/components/TermsOfService/index.ts         |   5 +-
 9 files changed, 286 insertions(+), 159 deletions(-)

diff --git a/packages/merchant-backoffice-ui/src/Application.tsx 
b/packages/merchant-backoffice-ui/src/Application.tsx
index e832d3107..0c509ef45 100644
--- a/packages/merchant-backoffice-ui/src/Application.tsx
+++ b/packages/merchant-backoffice-ui/src/Application.tsx
@@ -19,14 +19,16 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { HttpStatusCode, LibtoolVersion } from "@gnu-taler/taler-util";
+import { HttpStatusCode, LibtoolVersion, TranslatedString } from 
"@gnu-taler/taler-util";
 import {
   ErrorType,
   TranslationProvider,
+  notifyError,
+  notifyException,
   useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
-import { useMemo } from "preact/hooks";
+import { useEffect, useErrorBoundary, useMemo } from "preact/hooks";
 import { ApplicationReadyRoutes } from "./ApplicationReadyRoutes.js";
 import { Loading } from "./components/exception/loading.js";
 import {
diff --git a/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx 
b/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx
index 47177e97e..3dc34d1a9 100644
--- a/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx
+++ b/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx
@@ -18,12 +18,12 @@
  *
  * @author Sebastian Javier Marchano (sebasjm)
  */
-import { HttpStatusCode } from "@gnu-taler/taler-util";
+import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
 import { ErrorType, useTranslationContext } from "@gnu-taler/web-util/browser";
 import { createHashHistory } from "history";
 import { Fragment, VNode, h } from "preact";
 import { Route, Router, route } from "preact-router";
-import { useState } from "preact/hooks";
+import { useEffect, useErrorBoundary, useState } from "preact/hooks";
 import { InstanceRoutes } from "./InstanceRoutes.js";
 import {
   NotConnectedAppMenu,
@@ -54,7 +54,6 @@ export function ApplicationReadyRoutes(): VNode {
     updateToken(token)
     setUnauthorized(false)
   }
-
   const result = useBackendInstancesTestForAdmin();
 
   const clearTokenAndGoToRoot = () => {
diff --git a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx 
b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
index c3c20bcc4..b5680eabb 100644
--- a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
+++ b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
@@ -23,11 +23,12 @@ import {
   useTranslationContext,
   HttpError,
   ErrorType,
+  GlobalNotificationsBanner,
 } from "@gnu-taler/web-util/browser";
 import { format } from "date-fns";
 import { Fragment, FunctionComponent, h, VNode } from "preact";
 import { Route, route, Router } from "preact-router";
-import { useCallback, useEffect, useMemo, useState } from "preact/hooks";
+import { useCallback, useEffect, useErrorBoundary, useMemo, useState } from 
"preact/hooks";
 import { Loading } from "./components/exception/loading.js";
 import { Menu, NotificationCard } from "./components/menu/index.js";
 import { useBackendContext } from "./context/backend.js";
@@ -77,6 +78,7 @@ import { Notification } from "./utils/types.js";
 import { LoginToken, MerchantBackend } from "./declaration.js";
 import { Settings } from "./paths/settings/index.js";
 import { dateFormatForSettings, useSettings } from "./hooks/useSettings.js";
+import { TranslatedString } from "@gnu-taler/taler-util";
 
 export enum InstancePaths {
   error = "/error",
@@ -151,7 +153,7 @@ export function InstanceRoutes({
   const [token, updateToken] = useBackendInstanceToken(id);
   const { i18n } = useTranslationContext();
 
-  type GlobalNotifState = (Notification & { to: string }) | undefined;
+  type GlobalNotifState = (Notification & { to: string | undefined }) | 
undefined;
   const [globalNotification, setGlobalNotification] =
     useState<GlobalNotifState>(undefined);
 
@@ -163,9 +165,8 @@ export function InstanceRoutes({
     }
     onLoginPass()
   };
-  // const updateLoginStatus = (url: string, token?: string) => {
-  //   changeToken(token);
-  // };
+
+  const [error] = useErrorBoundary();
 
   const value = useMemo(
     () => ({ id, token, admin, changeToken }),
@@ -264,6 +265,15 @@ export function InstanceRoutes({
       />
       <KycBanner />
       <NotificationCard notification={globalNotification} />
+      {error &&
+        <NotificationCard notification={{
+          message: "Internal error, please repot",
+          type: "ERROR",
+          description: <pre>
+            {(error instanceof Error ? error.stack : String(error)) as 
TranslatedString}
+          </pre>
+        }} />
+      }
 
       <Router
         onChange={(e) => {
diff --git 
a/packages/merchant-backoffice-ui/src/components/form/InputDuration.tsx 
b/packages/merchant-backoffice-ui/src/components/form/InputDuration.tsx
index 7aa2703a4..c9226ad69 100644
--- a/packages/merchant-backoffice-ui/src/components/form/InputDuration.tsx
+++ b/packages/merchant-backoffice-ui/src/components/form/InputDuration.tsx
@@ -58,6 +58,9 @@ export function InputDuration<T>({
   } else if (value.d_ms === "forever") {
     strValue = i18n.str`forever`;
   } else {
+    if (value.d_ms === undefined) {
+      throw Error(`assertion error: duration should have a d_ms but got 
'${JSON.stringify(value)}'`)
+    }
     strValue = formatDuration(
       intervalToDuration({ start: 0, end: value.d_ms }),
       {
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx
index 5f1ae26a3..94424132b 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx
@@ -28,16 +28,11 @@ import {
   FormProvider,
 } from "../../../../components/form/FormProvider.js";
 import { Input } from "../../../../components/form/Input.js";
-import { InputCurrency } from "../../../../components/form/InputCurrency.js";
-import { InputDuration } from "../../../../components/form/InputDuration.js";
-import { InputNumber } from "../../../../components/form/InputNumber.js";
-import { useBackendContext } from "../../../../context/backend.js";
-import { MerchantBackend } from "../../../../declaration.js";
 import { InputSelector } from "../../../../components/form/InputSelector.js";
 import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
+import { useBackendContext } from "../../../../context/backend.js";
+import { MerchantBackend } from "../../../../declaration.js";
 import { isBase32RFC3548Charset, randomBase32Key } from 
"../../../../utils/crypto.js";
-import { QR } from "../../../../components/exception/QR.js";
-import { useInstanceContext } from "../../../../context/instance.js";
 
 type Entity = MerchantBackend.OTP.OtpDeviceAddDetails;
 
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
index 947f3572c..a2c0c9dfb 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
@@ -20,8 +20,11 @@
  */
 
 import {
+  AmountString,
   Amounts,
+  Duration,
   MerchantTemplateContractDetails,
+  assertUnreachable,
 } from "@gnu-taler/taler-util";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
@@ -50,10 +53,20 @@ enum Steps {
   NON_FIXED,
 }
 
-type Entity = MerchantBackend.Template.TemplateAddDetails & { type: Steps };
+// type Entity = MerchantBackend.Template.TemplateAddDetails & { type: Steps };
+type Entity = {
+  id?: string,
+  description?: string,
+  otpId?: string,
+  summary?: string,
+  amount?: AmountString,
+  minimum_age?: number,
+  pay_duration?: Duration,
+  type: Steps,
+};
 
 interface Props {
-  onCreate: (d: Entity) => Promise<void>;
+  onCreate: (d: MerchantBackend.Template.TemplateAddDetails) => Promise<void>;
   onBack?: () => void;
 }
 
@@ -63,57 +76,51 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
   const devices = useInstanceOtpDevices()
 
   const [state, setState] = useState<Partial<Entity>>({
-    template_contract: {
-      minimum_age: 0,
-      pay_duration: {
-        d_us: 1000 * 1000 * 60 * 30, //30 min
-      },
+    minimum_age: 0,
+    pay_duration: {
+      d_ms: 1000 * 60 * 30, //30 min
     },
     type: Steps.NON_FIXED,
   });
 
-  const parsedPrice = !state.template_contract?.amount
+  const parsedPrice = !state.amount
     ? undefined
-    : Amounts.parse(state.template_contract?.amount);
+    : Amounts.parse(state.amount);
 
   const errors: FormErrors<Entity> = {
-    template_id: !state.template_id
+    id: !state.id
       ? i18n.str`should not be empty`
-      : !/[a-zA-Z0-9]*/.test(state.template_id)
+      : !/[a-zA-Z0-9]*/.test(state.id)
         ? i18n.str`no valid. only characters and numbers`
         : undefined,
-    template_description: !state.template_description
+    description: !state.description
       ? i18n.str`should not be empty`
       : undefined,
-    template_contract: !state.template_contract
+    amount: !(state.type === Steps.FIXED_PRICE || state.type === 
Steps.BOTH_FIXED)
       ? undefined
-      : undefinedIfEmpty({
-        amount: !(state.type === Steps.FIXED_PRICE || state.type === 
Steps.BOTH_FIXED)
-          ? undefined
-          : !state.template_contract?.amount
-            ? i18n.str`required`
-            : !parsedPrice
-              ? i18n.str`not valid`
-              : Amounts.isZero(parsedPrice)
-                ? i18n.str`must be greater than 0`
-                : undefined,
-        summary: !(state.type === Steps.FIXED_SUMMARY || state.type === 
Steps.BOTH_FIXED)
-          ? undefined
-          : !state.template_contract?.summary
-            ? i18n.str`required`
+      : !state.amount
+        ? i18n.str`required`
+        : !parsedPrice
+          ? i18n.str`not valid`
+          : Amounts.isZero(parsedPrice)
+            ? i18n.str`must be greater than 0`
             : undefined,
-        minimum_age:
-          state.template_contract.minimum_age < 0
-            ? i18n.str`should be greater that 0`
-            : undefined,
-        pay_duration: !state.template_contract.pay_duration
-          ? i18n.str`can't be empty`
-          : state.template_contract.pay_duration.d_us === "forever"
-            ? undefined
-            : state.template_contract.pay_duration.d_us < 1000 * 1000 //less 
than one second
-              ? i18n.str`to short`
-              : undefined,
-      } as Partial<MerchantTemplateContractDetails>),
+    summary: !(state.type === Steps.FIXED_SUMMARY || state.type === 
Steps.BOTH_FIXED)
+      ? undefined
+      : !state.summary
+        ? i18n.str`required`
+        : undefined,
+    minimum_age:
+      state.minimum_age && state.minimum_age < 0
+        ? i18n.str`should be greater that 0`
+        : undefined,
+    pay_duration: !state.pay_duration
+      ? i18n.str`can't be empty`
+      : state.pay_duration.d_ms === "forever"
+        ? undefined
+        : state.pay_duration.d_ms < 1000 //less than one second
+          ? i18n.str`to short`
+          : undefined,
   };
 
   const hasErrors = Object.keys(errors).some(
@@ -121,21 +128,56 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
   );
 
   const submitForm = () => {
-    if (hasErrors) return Promise.reject();
-    if (state.template_contract) {
-      if (state.type === Steps.NON_FIXED) {
-        delete state.template_contract.amount;
-        delete state.template_contract.summary;
-      } else if (state.type === Steps.FIXED_SUMMARY) {
-        delete state.template_contract.amount;
-      } else if (state.type === Steps.FIXED_PRICE) {
-        delete state.template_contract.summary;
-      }
-    }
-    delete state.type
-    return onCreate(state as any);
-  };
-
+    if (hasErrors || state.type === undefined) return Promise.reject();
+    switch (state.type) {
+      case Steps.FIXED_PRICE: return onCreate({
+        template_id: state.id!,
+        template_description: state.description!,
+        template_contract: {
+          minimum_age: state.minimum_age!,
+          pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
+          amount: state.amount!,
+          // summary: state.summary,
+        },
+        otp_id: state.otpId!
+      })
+      case Steps.FIXED_SUMMARY: return onCreate({
+        template_id: state.id!,
+        template_description: state.description!,
+        template_contract: {
+          minimum_age: state.minimum_age!,
+          pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
+          // amount: state.amount!,
+          summary: state.summary,
+        },
+        otp_id: state.otpId!,
+      })
+      case Steps.NON_FIXED: return onCreate({
+        template_id: state.id!,
+        template_description: state.description!,
+        template_contract: {
+          minimum_age: state.minimum_age!,
+          pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
+          // amount: state.amount!,
+          // summary: state.summary,
+        },
+        otp_id: state.otpId!,
+      })
+      case Steps.BOTH_FIXED: return onCreate({
+        template_id: state.id!,
+        template_description: state.description!,
+        template_contract: {
+          minimum_age: state.minimum_age!,
+          pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
+          amount: state.amount!,
+          summary: state.summary,
+        },
+        otp_id: state.otpId!,
+      })
+      default: assertUnreachable(state.type)
+      // return onCreate(state);
+    };
+  }
   const deviceList = !devices.ok ? [] : devices.data.otp_devices
 
   return (
@@ -150,21 +192,22 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
               errors={errors}
             >
               <InputWithAddon<Entity>
-                name="template_id"
-                help={`${backendURL}/templates/${state.template_id ?? ""}`}
+                name="id"
+                help={`${backendURL}/templates/${state.id ?? ""}`}
                 label={i18n.str`Identifier`}
                 tooltip={i18n.str`Name of the template in URLs.`}
               />
               <Input<Entity>
-                name="template_description"
+                name="description"
                 label={i18n.str`Description`}
                 help=""
                 tooltip={i18n.str`Describe what this template stands for`}
               />
-              <InputTab
+              <InputTab<Entity>
                 name="type"
                 label={i18n.str`Type`}
                 help={(() => {
+                  if (state.type === undefined) return ""
                   switch (state.type) {
                     case Steps.NON_FIXED: return i18n.str`User will be able to 
input price and summary before payment.`
                     case Steps.FIXED_PRICE: return i18n.str`User will be able 
to add a summary before payment.`
@@ -189,41 +232,52 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
                 }}
               />
               {state.type === Steps.BOTH_FIXED || state.type === 
Steps.FIXED_SUMMARY ?
-                <Input
-                  name="template_contract.summary"
+                <Input<Entity>
+                  name="summary"
                   inputType="multiline"
                   label={i18n.str`Fixed summary`}
                   tooltip={i18n.str`If specified, this template will create 
order with the same summary`}
                 />
                 : undefined}
               {state.type === Steps.BOTH_FIXED || state.type === 
Steps.FIXED_PRICE ?
-                <InputCurrency
-                  name="template_contract.amount"
+                <InputCurrency<Entity>
+                  name="amount"
                   label={i18n.str`Fixed price`}
                   tooltip={i18n.str`If specified, this template will create 
order with the same price`}
                 />
                 : undefined}
-              <InputNumber
-                name="template_contract.minimum_age"
+              <InputNumber<Entity>
+                name="minimum_age"
                 label={i18n.str`Minimum age`}
                 help=""
                 tooltip={i18n.str`Is this contract restricted to some age?`}
               />
-              <InputDuration
-                name="template_contract.pay_duration"
+              <InputDuration<Entity>
+                name="pay_duration"
                 label={i18n.str`Payment timeout`}
                 help=""
                 tooltip={i18n.str`How much time has the customer to complete 
the payment once the order was created.`}
               />
               <Input<Entity>
-                name="otp_id"
+                name="otpId"
                 label={i18n.str`OTP device`}
                 readonly
+                side={<button
+                  class="button is-danger"
+                  data-tooltip={i18n.str`without otp device`}
+                  onClick={(): void => {
+                    setState((v) => ({ ...v, otpId: undefined }));
+                  }}
+                >
+                  <span>
+                    <i18n.Translate>remove</i18n.Translate>
+                  </span>
+                </button>}
                 tooltip={i18n.str`Use to verify transaction in offline mode.`}
               />
               <InputSearchOnList
                 label={i18n.str`Search device`}
-                onChange={(p) => setState((v) => ({ ...v, otp_id: p?.id }))}
+                onChange={(p) => setState((v) => ({ ...v, otpId: p?.id }))}
                 list={deviceList.map(e => ({
                   description: e.device_description,
                   id: e.otp_device_id
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx
index b9767442f..2d50e3924 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx
@@ -86,7 +86,7 @@ export default function ListTemplates({
       <NotificationCard notification={notif} />
 
       <JumpToElementById
-        testIfExist={testTemplateExist} 
+        testIfExist={testTemplateExist}
         onSelect={onSelect}
         description={i18n.str`jump to template with the given template ID`}
         palceholder={i18n.str`template id`}
diff --git 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx
 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx
index b578d4664..cfb521c73 100644
--- 
a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx
+++ 
b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx
@@ -20,8 +20,10 @@
  */
 
 import {
+  AmountString,
   Amounts,
-  MerchantTemplateContractDetails,
+  Duration,
+  assertUnreachable
 } from "@gnu-taler/taler-util";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
@@ -35,11 +37,12 @@ import { Input } from 
"../../../../components/form/Input.js";
 import { InputCurrency } from "../../../../components/form/InputCurrency.js";
 import { InputDuration } from "../../../../components/form/InputDuration.js";
 import { InputNumber } from "../../../../components/form/InputNumber.js";
+import { InputTab } from "../../../../components/form/InputTab.js";
 import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
 import { useBackendContext } from "../../../../context/backend.js";
-import { MerchantBackend, WithId } from "../../../../declaration.js";
-import { undefinedIfEmpty } from "../../../../utils/table.js";
-import { InputTab } from "../../../../components/form/InputTab.js";
+import { MerchantBackend } from "../../../../declaration.js";
+import { InputSearchOnList } from 
"../../../../components/form/InputSearchOnList.js";
+import { useInstanceOtpDevices } from "../../../../hooks/otp.js";
 
 enum Steps {
   BOTH_FIXED,
@@ -48,12 +51,19 @@ enum Steps {
   NON_FIXED,
 }
 
-type Entity = MerchantBackend.Template.TemplatePatchDetails & WithId;
+type Entity = {
+  description?: string,
+  otpId?: string | null,
+  summary?: string,
+  amount?: AmountString,
+  minimum_age?: number,
+  pay_duration?: Duration,
+};
 
 interface Props {
-  onUpdate: (d: Entity) => Promise<void>;
+  onUpdate: (d: MerchantBackend.Template.TemplatePatchDetails) => 
Promise<void>;
   onBack?: () => void;
-  template: Entity;
+  template: MerchantBackend.Template.TemplateDetails;
 }
 
 export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
@@ -61,53 +71,59 @@ export function UpdatePage({ template, onUpdate, onBack }: 
Props): VNode {
   const { url: backendURL } = useBackendContext()
 
   const intialStep =
-    template.template_contract?.amount === undefined && 
template.template_contract?.summary === undefined
+    template.template_contract.amount === undefined && 
template.template_contract.summary === undefined
       ? Steps.NON_FIXED
-      : template.template_contract?.summary === undefined
+      : template.template_contract.summary === undefined
         ? Steps.FIXED_PRICE
-        : template.template_contract?.amount === undefined
+        : template.template_contract.amount === undefined
           ? Steps.FIXED_SUMMARY
           : Steps.BOTH_FIXED;
 
-  const [state, setState] = useState<Partial<Entity & { type: Steps }>>({ 
...template, type: intialStep });
+  const [state, setState] = useState<Partial<Entity & { type: Steps }>>({
+    amount: template.template_contract.amount as AmountString | undefined,
+    description: template.template_description,
+    minimum_age: template.template_contract.minimum_age,
+    otpId: template.otp_id,
+    pay_duration: template.template_contract.pay_duration ? 
Duration.fromTalerProtocolDuration(template.template_contract.pay_duration) : 
undefined,
+    summary: template.template_contract.summary,
+    type: intialStep,
+  });
+  const devices = useInstanceOtpDevices()
+  const deviceList = !devices.ok ? [] : devices.data.otp_devices
 
-  const parsedPrice = !state.template_contract?.amount
+  const parsedPrice = !state.amount
     ? undefined
-    : Amounts.parse(state.template_contract?.amount);
+    : Amounts.parse(state.amount);
 
   const errors: FormErrors<Entity> = {
-    template_description: !state.template_description
+    description: !state.description
       ? i18n.str`should not be empty`
       : undefined,
-    template_contract: !state.template_contract
+    amount: !(state.type === Steps.FIXED_PRICE || state.type === 
Steps.BOTH_FIXED)
       ? undefined
-      : undefinedIfEmpty({
-        amount: !(state.type === Steps.FIXED_PRICE || state.type === 
Steps.BOTH_FIXED)
-          ? undefined
-          : !state.template_contract?.amount
-            ? i18n.str`required`
-            : !parsedPrice
-              ? i18n.str`not valid`
-              : Amounts.isZero(parsedPrice)
-                ? i18n.str`must be greater than 0`
-                : undefined,
-        summary: !(state.type === Steps.FIXED_SUMMARY || state.type === 
Steps.BOTH_FIXED)
-          ? undefined
-          : !state.template_contract?.summary
-            ? i18n.str`required`
-            : undefined,
-        minimum_age:
-          state.template_contract.minimum_age < 0
-            ? i18n.str`should be greater that 0`
+      : !state.amount
+        ? i18n.str`required`
+        : !parsedPrice
+          ? i18n.str`not valid`
+          : Amounts.isZero(parsedPrice)
+            ? i18n.str`must be greater than 0`
             : undefined,
-        pay_duration: !state.template_contract.pay_duration
-          ? i18n.str`can't be empty`
-          : state.template_contract.pay_duration.d_us === "forever"
-            ? undefined
-            : state.template_contract.pay_duration.d_us < 1000 * 1000 // less 
than one second
-              ? i18n.str`to short`
-              : undefined,
-      } as Partial<MerchantTemplateContractDetails>),
+    summary: !(state.type === Steps.FIXED_SUMMARY || state.type === 
Steps.BOTH_FIXED)
+      ? undefined
+      : !state.summary
+        ? i18n.str`required`
+        : undefined,
+    minimum_age:
+      state.minimum_age && state.minimum_age < 0
+        ? i18n.str`should be greater that 0`
+        : undefined,
+    pay_duration: !state.pay_duration
+      ? i18n.str`can't be empty`
+      : state.pay_duration.d_ms === "forever"
+        ? undefined
+        : state.pay_duration.d_ms < 1000 // less than one second
+          ? i18n.str`to short`
+          : undefined,
   };
 
   const hasErrors = Object.keys(errors).some(
@@ -115,19 +131,50 @@ export function UpdatePage({ template, onUpdate, onBack 
}: Props): VNode {
   );
 
   const submitForm = () => {
-    if (hasErrors) return Promise.reject();
-    if (state.template_contract) {
-      if (state.type === Steps.NON_FIXED) {
-        delete state.template_contract.amount;
-        delete state.template_contract.summary;
-      } else if (state.type === Steps.FIXED_SUMMARY) {
-        delete state.template_contract.amount;
-      } else if (state.type === Steps.FIXED_PRICE) {
-        delete state.template_contract.summary;
-      }
+    if (hasErrors || state.type === undefined) return Promise.reject();
+    switch (state.type) {
+      case Steps.FIXED_PRICE: return onUpdate({
+        template_description: state.description!,
+        template_contract: {
+          minimum_age: state.minimum_age!,
+          pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
+          amount: state.amount!,
+          // summary: state.summary,
+        },
+        otp_id: state.otpId!
+      })
+      case Steps.FIXED_SUMMARY: return onUpdate({
+        template_description: state.description!,
+        template_contract: {
+          minimum_age: state.minimum_age!,
+          pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
+          // amount: state.amount!,
+          summary: state.summary,
+        },
+        otp_id: state.otpId!,
+      })
+      case Steps.NON_FIXED: return onUpdate({
+        template_description: state.description!,
+        template_contract: {
+          minimum_age: state.minimum_age!,
+          pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
+          // amount: state.amount!,
+          // summary: state.summary,
+        },
+        otp_id: state.otpId!,
+      })
+      case Steps.BOTH_FIXED: return onUpdate({
+        template_description: state.description!,
+        template_contract: {
+          minimum_age: state.minimum_age!,
+          pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
+          amount: state.amount!,
+          summary: state.summary,
+        },
+        otp_id: state.otpId!,
+      })
+      default: assertUnreachable(state.type)
     }
-    delete state.type
-    return onUpdate(state as any);
   };
 
 
@@ -140,7 +187,7 @@ export function UpdatePage({ template, onUpdate, onBack }: 
Props): VNode {
               <div class="level-left">
                 <div class="level-item">
                   <span class="is-size-4">
-                    {backendURL}/templates/{template.id}
+                    {backendURL}/templates/{template.otp_id}
                   </span>
                 </div>
               </div>
@@ -157,16 +204,9 @@ export function UpdatePage({ template, onUpdate, onBack }: 
Props): VNode {
                 valueHandler={setState}
                 errors={errors}
               >
-                <InputWithAddon<Entity>
-                  name="id"
-                  addonBefore={`templates/`}
-                  readonly
-                  label={i18n.str`Identifier`}
-                  tooltip={i18n.str`Name of the template in URLs.`}
-                />
 
                 <Input<Entity>
-                  name="template_description"
+                  name="description"
                   label={i18n.str`Description`}
                   help=""
                   tooltip={i18n.str`Describe what this template stands for`}
@@ -199,32 +239,57 @@ export function UpdatePage({ template, onUpdate, onBack 
}: Props): VNode {
                   }}
                 />
                 {state.type === Steps.BOTH_FIXED || state.type === 
Steps.FIXED_SUMMARY ?
-                  <Input
-                    name="template_contract.summary"
+                  <Input<Entity>
+                    name="summary"
                     inputType="multiline"
                     label={i18n.str`Fixed summary`}
                     tooltip={i18n.str`If specified, this template will create 
order with the same summary`}
                   />
                   : undefined}
                 {state.type === Steps.BOTH_FIXED || state.type === 
Steps.FIXED_PRICE ?
-                  <InputCurrency
-                    name="template_contract.amount"
+                  <InputCurrency<Entity>
+                    name="amount"
                     label={i18n.str`Fixed price`}
                     tooltip={i18n.str`If specified, this template will create 
order with the same price`}
                   />
                   : undefined}
-                <InputNumber
-                  name="template_contract.minimum_age"
+                <InputNumber<Entity>
+                  name="minimum_age"
                   label={i18n.str`Minimum age`}
                   help=""
                   tooltip={i18n.str`Is this contract restricted to some age?`}
                 />
-                <InputDuration
-                  name="template_contract.pay_duration"
+                <InputDuration<Entity>
+                  name="pay_duration"
                   label={i18n.str`Payment timeout`}
                   help=""
                   tooltip={i18n.str`How much time has the customer to complete 
the payment once the order was created.`}
                 />
+                <Input<Entity>
+                  name="otpId"
+                  label={i18n.str`OTP device`}
+                  readonly
+                  side={<button
+                    class="button is-danger"
+                    data-tooltip={i18n.str`remove otp device for this 
template`}
+                    onClick={(): void => {
+                      setState((v) => ({ ...v, otpId: null }));
+                    }}
+                  >
+                    <span>
+                      <i18n.Translate>remove</i18n.Translate>
+                    </span>
+                  </button>}
+                  tooltip={i18n.str`Use to verify transaction in offline 
mode.`}
+                />
+                <InputSearchOnList
+                  label={i18n.str`Search device`}
+                  onChange={(p) => setState((v) => ({ ...v, otpId: p?.id }))}
+                  list={deviceList.map(e => ({
+                    description: e.device_description,
+                    id: e.otp_device_id
+                  }))}
+                />
               </FormProvider>
 
               <div class="buttons is-right mt-5">
diff --git 
a/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts 
b/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts
index 6aac07cfe..1c76d4d95 100644
--- a/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts
+++ b/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts
@@ -14,11 +14,11 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { ExchangeListItem } from "@gnu-taler/taler-util";
+import { ComponentChildren } from "preact";
 import { Loading } from "../../components/Loading.js";
 import { ErrorAlert } from "../../context/alert.js";
 import { SelectFieldHandler, ToggleHandler } from "../../mui/handlers.js";
-import { compose, StateViewMap } from "../../utils/index.js";
+import { StateViewMap, compose } from "../../utils/index.js";
 import { ErrorAlertView } from "../CurrentAlerts.js";
 import { useComponentState } from "./state.js";
 import { TermsState } from "./utils.js";
@@ -27,7 +27,6 @@ import {
   ShowButtonsNonAcceptedTosView,
   ShowTosContentView,
 } from "./views.js";
-import { ComponentChildren } from "preact";
 
 export interface Props {
   exchangeUrl: string;

-- 
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]