gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated (cb860500b -> e9c6b105d)


From: gnunet
Subject: [taler-wallet-core] branch master updated (cb860500b -> e9c6b105d)
Date: Thu, 18 Jan 2024 21:51:05 +0100

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

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

    from cb860500b fixed last auditor-error
     new 421b5c0ac fix templates
     new e9c6b105d update config from merchant

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../merchant-backoffice-ui/src/Application.tsx     |  14 +-
 .../src/ApplicationReadyRoutes.tsx                 |   5 +-
 .../merchant-backoffice-ui/src/InstanceRoutes.tsx  |  20 +-
 .../src/components/form/InputDuration.tsx          |   3 +
 .../merchant-backoffice-ui/src/context/config.ts   |   9 +-
 .../merchant-backoffice-ui/src/declaration.d.ts    |  40 +++-
 .../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 +-
 11 files changed, 332 insertions(+), 170 deletions(-)

diff --git a/packages/merchant-backoffice-ui/src/Application.tsx 
b/packages/merchant-backoffice-ui/src/Application.tsx
index e832d3107..27ae26de5 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 {
@@ -59,10 +61,10 @@ function ApplicationStatusRoutes(): VNode {
   const result = useBackendConfig();
   const { i18n } = useTranslationContext();
 
-  const { currency, version } = result.ok && result.data
+  const configData = result.ok && result.data
     ? result.data
-    : { currency: "unknown", version: "unknown" };
-  const ctx = useMemo(() => ({ currency, version }), [currency, version]);
+    : undefined;
+  const ctx = useMemo(() => (configData), [configData]);
 
   if (!result.ok) {
     if (result.loading) return <Loading />;
@@ -157,7 +159,7 @@ function ApplicationStatusRoutes(): VNode {
 
   return (
     <div class="has-navbar-fixed-top">
-      <ConfigContextProvider value={ctx}>
+      <ConfigContextProvider value={ctx!}>
         <ApplicationReadyRoutes />
       </ConfigContextProvider>
     </div>
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/context/config.ts 
b/packages/merchant-backoffice-ui/src/context/config.ts
index 040bd0341..9fe655301 100644
--- a/packages/merchant-backoffice-ui/src/context/config.ts
+++ b/packages/merchant-backoffice-ui/src/context/config.ts
@@ -21,12 +21,9 @@
 
 import { createContext } from "preact";
 import { useContext } from "preact/hooks";
+import { MerchantBackend } from "../declaration.js";
 
-interface Type {
-  currency: string;
-  version: string;
-}
-const Context = createContext<Type>(null!);
+const Context = createContext<MerchantBackend.VersionResponse>(null!);
 
 export const ConfigContextProvider = Context.Provider;
-export const useConfigContext = (): Type => useContext(Context);
+export const useConfigContext = (): MerchantBackend.VersionResponse => 
useContext(Context);
diff --git a/packages/merchant-backoffice-ui/src/declaration.d.ts 
b/packages/merchant-backoffice-ui/src/declaration.d.ts
index f99dd1867..38ab9d254 100644
--- a/packages/merchant-backoffice-ui/src/declaration.d.ts
+++ b/packages/merchant-backoffice-ui/src/declaration.d.ts
@@ -274,8 +274,46 @@ export namespace MerchantBackend {
     // Name of the protocol.
     name: "taler-merchant";
 
-    // Currency supported by this backend.
+    // Default (!) currency supported by this backend.
+    // This is the currency that the backend should
+    // suggest by default to the user when entering
+    // amounts. See currencies for a list of
+    // supported currencies and how to render them.
     currency: string;
+
+    // How services should render currencies supported
+    // by this backend.  Maps
+    // currency codes (e.g. "EUR" or "KUDOS") to
+    // the respective currency specification.
+    // All currencies in this map are supported by
+    // the backend.  Note that the actual currency
+    // specifications are a *hint* for applications
+    // that would like *advice* on how to render amounts.
+    // Applications *may* ignore the currency specification
+    // if they know how to render currencies that they are
+    // used with.
+    currencies: { currency: CurrencySpecification };
+
+    // Array of exchanges trusted by the merchant.
+    // Since protocol v6.
+    exchanges: ExchangeConfigInfo[];
+  }
+
+  interface ExchangeConfigInfo {
+
+    // Base URL of the exchange REST API.
+    base_url: string;
+
+    // Currency for which the merchant is configured
+    // to trust the exchange.
+    // May not be the one the exchange actually uses,
+    // but is the only one we would trust this exchange for.
+    currency: string;
+
+    // Offline master public key of the exchange. The
+    // /keys data must be signed with this public
+    // key for us to trust it.
+    master_pub: EddsaPublicKey;
   }
   interface Location {
     // Nation with its own government.
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]