From e1d86816a7c07cb8ca2d54676d5cdbbe513f2ba7 Mon Sep 17 00:00:00 2001
From: Sebastian <sebasjm@gmail.com>
Date: Mon, 4 Sep 2023 14:17:55 -0300
Subject: backoffcie new version, lot of changes

---
 .../paths/instance/templates/create/CreatePage.tsx | 127 +++++----------------
 1 file changed, 27 insertions(+), 100 deletions(-)

(limited to 'packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx')

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 e20b9bc27..8629d8dee 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
@@ -24,7 +24,7 @@ import {
   MerchantTemplateContractDetails,
 } from "@gnu-taler/taler-util";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Fragment, h, VNode } from "preact";
+import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
 import {
@@ -35,17 +35,16 @@ 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 { InputSelector } from "../../../../components/form/InputSelector.js";
 import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
 import { useBackendContext } from "../../../../context/backend.js";
+import { useInstanceContext } from "../../../../context/instance.js";
 import { MerchantBackend } from "../../../../declaration.js";
 import {
-  isBase32RFC3548Charset,
-  randomBase32Key,
+  isBase32RFC3548Charset
 } from "../../../../utils/crypto.js";
 import { undefinedIfEmpty } from "../../../../utils/table.js";
-import { QR } from "../../../../components/exception/QR.js";
-import { useInstanceContext } from "../../../../context/instance.js";
+import { InputSearchOnList } from "../../../../components/form/InputSearchOnList.js";
+import { useInstanceOtpDevices } from "../../../../hooks/otp.js";
 
 type Entity = MerchantBackend.Template.TemplateAddDetails;
 
@@ -54,16 +53,11 @@ interface Props {
   onBack?: () => void;
 }
 
-const algorithms = [0, 1, 2];
-const algorithmsNames = ["off", "30s 8d TOTP-SHA1", "30s 8d eTOTP-SHA1"];
-
 export function CreatePage({ onCreate, onBack }: Props): VNode {
   const { i18n } = useTranslationContext();
   const backend = useBackendContext();
-  const { id: instanceId } = useInstanceContext();
-  const issuer = new URL(backend.url).hostname;
+  const devices = useInstanceOtpDevices()
 
-  const [showKey, setShowKey] = useState(false);
   const [state, setState] = useState<Partial<Entity>>({
     template_contract: {
       minimum_age: 0,
@@ -78,7 +72,11 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
     : Amounts.parse(state.template_contract?.amount);
 
   const errors: FormErrors<Entity> = {
-    template_id: !state.template_id ? i18n.str`should not be empty` : undefined,
+    template_id: !state.template_id
+      ? i18n.str`should not be empty`
+      : !/[a-zA-Z0-9]*/.test(state.template_id)
+        ? i18n.str`no valid. only characters and numbers`
+        : undefined,
     template_description: !state.template_description
       ? i18n.str`should not be empty`
       : undefined,
@@ -104,15 +102,6 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
               ? i18n.str`to short`
               : undefined,
       } as Partial<MerchantTemplateContractDetails>),
-    pos_key: !state.pos_key
-      ? !state.pos_algorithm
-        ? undefined
-        : i18n.str`required`
-      : !isBase32RFC3548Charset(state.pos_key)
-        ? i18n.str`just letters and numbers from 2 to 7`
-        : state.pos_key.length !== 32
-          ? i18n.str`size of the key should be 32`
-          : undefined,
   };
 
   const hasErrors = Object.keys(errors).some(
@@ -124,7 +113,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
     return onCreate(state as any);
   };
 
-  const qrText = `otpauth://totp/${instanceId}/${state.template_id}?issuer=${issuer}&algorithm=SHA1&digits=8&period=30&secret=${state.pos_key}`;
+  const deviceList = !devices.ok ? [] : devices.data.otp_devices
 
   return (
     <div>
@@ -139,7 +128,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
             >
               <InputWithAddon<Entity>
                 name="template_id"
-                help={`${backend.url}/instances/templates/${state.template_id ?? ""}`}
+                help={`${backend.url}/templates/${state.template_id ?? ""}`}
                 label={i18n.str`Identifier`}
                 tooltip={i18n.str`Name of the template in URLs.`}
               />
@@ -172,83 +161,21 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
                 help=""
                 tooltip={i18n.str`How much time has the customer to complete the payment once the order was created.`}
               />
-              <InputSelector<Entity>
-                name="pos_algorithm"
-                label={i18n.str`Verification algorithm`}
-                tooltip={i18n.str`Algorithm to use to verify transaction in offline mode`}
-                values={algorithms}
-                toStr={(v) => algorithmsNames[v]}
-                fromStr={(v) => Number(v)}
+              <Input<Entity>
+                name="otp_id"
+                label={i18n.str`OTP device`}
+                readonly
+                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 }))}
+                list={deviceList.map(e => ({
+                  description: e.device_description,
+                  id: e.otp_device_id
+                }))}
               />
-              {state.pos_algorithm && state.pos_algorithm > 0 ? (
-                <Fragment>
-                  <InputWithAddon<Entity>
-                    name="pos_key"
-                    label={i18n.str`Point-of-sale key`}
-                    inputType={showKey ? "text" : "password"}
-                    help="Be sure to be very hard to guess or use the random generator"
-                    tooltip={i18n.str`Useful to validate the purchase`}
-                    fromStr={(v) => v.toUpperCase()}
-                    addonAfter={
-                      <span class="icon">
-                        {showKey ? (
-                          <i class="mdi mdi-eye" />
-                        ) : (
-                          <i class="mdi mdi-eye-off" />
-                        )}
-                      </span>
-                    }
-                    side={
-                      <span style={{ display: "flex" }}>
-                        <button
-                          data-tooltip={i18n.str`generate random secret key`}
-                          class="button is-info mr-3"
-                          onClick={(e) => {
-                            const pos_key = randomBase32Key();
-                            setState((s) => ({ ...s, pos_key }));
-                          }}
-                        >
-                          <i18n.Translate>random</i18n.Translate>
-                        </button>
-                        <button
-                          data-tooltip={
-                            showKey
-                              ? i18n.str`show secret key`
-                              : i18n.str`hide secret key`
-                          }
-                          class="button is-info mr-3"
-                          onClick={(e) => {
-                            setShowKey(!showKey);
-                          }}
-                        >
-                          {showKey ? (
-                            <i18n.Translate>hide</i18n.Translate>
-                          ) : (
-                            <i18n.Translate>show</i18n.Translate>
-                          )}
-                        </button>
-                      </span>
-                    }
-                  />
-                  {showKey && (
-                    <Fragment>
-                      <QR text={qrText} />
-                      <div
-                        style={{
-                          color: "grey",
-                          fontSize: "small",
-                          width: 200,
-                          textAlign: "center",
-                          margin: "auto",
-                          wordBreak: "break-all",
-                        }}
-                      >
-                        {qrText}
-                      </div>
-                    </Fragment>
-                  )}
-                </Fragment>
-              ) : undefined}
+
             </FormProvider>
 
             <div class="buttons is-right mt-5">
-- 
cgit v1.2.3

