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

---
 .../src/components/form/InputPaytoForm.tsx         | 305 +++++----------------
 1 file changed, 70 insertions(+), 235 deletions(-)

(limited to 'packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx')

diff --git a/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx b/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx
index 8d324660e..5cd69a0b3 100644
--- a/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx
+++ b/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx
@@ -18,9 +18,9 @@
  *
  * @author Sebastian Javier Marchano (sebasjm)
  */
+import { parsePaytoUri, PaytoUriGeneric, stringifyPaytoUri } from "@gnu-taler/taler-util";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Fragment, h, VNode } from "preact";
-import { useCallback, useState } from "preact/hooks";
 import { COUNTRY_TABLE } from "../../utils/constants.js";
 import { undefinedIfEmpty } from "../../utils/table.js";
 import { FormErrors, FormProvider } from "./FormProvider.js";
@@ -28,23 +28,23 @@ import { Input } from "./Input.js";
 import { InputGroup } from "./InputGroup.js";
 import { InputSelector } from "./InputSelector.js";
 import { InputProps, useField } from "./useField.js";
-import { InputWithAddon } from "./InputWithAddon.js";
-import { MerchantBackend } from "../../declaration.js";
+import { useEffect, useState } from "preact/hooks";
 
 export interface Props<T> extends InputProps<T> {
   isValid?: (e: any) => boolean;
 }
 
+// type Entity = PaytoUriGeneric
 // https://datatracker.ietf.org/doc/html/rfc8905
 type Entity = {
   // iban, bitcoin, x-taler-bank. it defined the format
   target: string;
   // path1 if the first field to be used
-  path1: string;
+  path1?: string;
   // path2 if the second field to be used, optional
   path2?: string;
-  // options of the payto uri
-  options: {
+  // params of the payto uri
+  params: {
     "receiver-name"?: string;
     sender?: string;
     message?: string;
@@ -52,13 +52,6 @@ type Entity = {
     instruction?: string;
     [name: string]: string | undefined;
   };
-  auth: {
-    type: "unset" | "basic" | "none";
-    url?: string;
-    username?: string;
-    password?: string;
-    repeat?: string;
-  };
 };
 
 function isEthereumAddress(address: string) {
@@ -171,14 +164,10 @@ const targets = [
   "bitcoin",
   "ethereum",
 ];
-const accountAuthType = ["none", "basic"];
 const noTargetValue = targets[0];
-const defaultTarget: Partial<Entity> = {
+const defaultTarget: Entity = {
   target: noTargetValue,
-  options: {},
-  auth: {
-    type: "unset" as const,
-  },
+  params: {},
 };
 
 export function InputPaytoForm<T>({
@@ -187,110 +176,91 @@ export function InputPaytoForm<T>({
   label,
   tooltip,
 }: Props<keyof T>): VNode {
-  const { value: paytos, onChange, required } = useField<T>(name);
-
-  const [value, valueHandler] = useState<Partial<Entity>>(defaultTarget);
+  const { value: initialValueStr, onChange } = useField<T>(name);
 
-  let payToPath;
-  if (value.target === "iban" && value.path1) {
-    payToPath = `/${value.path1.toUpperCase()}`;
-  } else if (value.path1) {
-    if (value.path2) {
-      payToPath = `/${value.path1}/${value.path2}`;
-    } else {
-      payToPath = `/${value.path1}`;
-    }
+  const initialPayto = parsePaytoUri(initialValueStr ?? "")
+  const paths = !initialPayto ? [] : initialPayto.targetPath.split("/")
+  const initialPath1 = paths.length >= 1 ? paths[0] : undefined;
+  const initialPath2 = paths.length >= 2 ? paths[1] : undefined;
+  const initial: Entity = initialPayto === undefined ? defaultTarget : {
+    target: initialPayto.targetType,
+    params: initialPayto.params,
+    path1: initialPath1,
+    path2: initialPath2,
   }
-  const { i18n } = useTranslationContext();
+  const [value, setValue] = useState<Partial<Entity>>(initial)
 
-  const ops = value.options ?? {};
-  const url = tryUrl(`payto://${value.target}${payToPath}`);
-  if (url) {
-    Object.keys(ops).forEach((opt_key) => {
-      const opt_value = ops[opt_key];
-      if (opt_value) url.searchParams.set(opt_key, opt_value);
-    });
-  }
-  const paytoURL = !url ? "" : url.href;
+  const { i18n } = useTranslationContext();
 
   const errors: FormErrors<Entity> = {
     target:
-      value.target === noTargetValue && !paytos.length
+      value.target === noTargetValue
         ? i18n.str`required`
         : undefined,
     path1: !value.path1
       ? i18n.str`required`
       : value.target === "iban"
-      ? validateIBAN(value.path1, i18n)
-      : value.target === "bitcoin"
-      ? validateBitcoin(value.path1, i18n)
-      : value.target === "ethereum"
-      ? validateEthereum(value.path1, i18n)
-      : undefined,
+        ? validateIBAN(value.path1, i18n)
+        : value.target === "bitcoin"
+          ? validateBitcoin(value.path1, i18n)
+          : value.target === "ethereum"
+            ? validateEthereum(value.path1, i18n)
+            : undefined,
     path2:
       value.target === "x-taler-bank"
         ? !value.path2
           ? i18n.str`required`
           : undefined
         : undefined,
-    options: undefinedIfEmpty({
-      "receiver-name": !value.options?.["receiver-name"]
+    params: undefinedIfEmpty({
+      "receiver-name": !value.params?.["receiver-name"]
         ? i18n.str`required`
         : undefined,
     }),
-    auth: !value.auth
-      ? undefined
-      : undefinedIfEmpty({
-          username:
-            value.auth.type === "basic" && !value.auth.username
-              ? i18n.str`required`
-              : undefined,
-          password:
-            value.auth.type === "basic" && !value.auth.password
-              ? i18n.str`required`
-              : undefined,
-          repeat:
-            value.auth.type === "basic" && !value.auth.repeat
-              ? i18n.str`required`
-              : value.auth.repeat !== value.auth.password
-              ? i18n.str`is not the same`
-              : undefined,
-        }),
   };
 
   const hasErrors = Object.keys(errors).some(
     (k) => (errors as any)[k] !== undefined,
   );
+  const str = hasErrors || !value.target ? undefined : stringifyPaytoUri({
+    targetType: value.target,
+    targetPath: value.path2 ? `${value.path1}/${value.path2}` : (value.path1 ?? ""),
+    params: value.params ?? {} as any,
+    isKnown: false,
+  })
+  useEffect(() => {
+    onChange(str as any)
+  }, [str])
 
-  const submit = useCallback((): void => {
-    const accounts: MerchantBackend.Instances.MerchantBankAccount[] = paytos;
-    const alreadyExists =
-      accounts.findIndex((x) => x.payto_uri === paytoURL) !== -1;
-    if (!alreadyExists) {
-      const newValue: MerchantBackend.Instances.MerchantBankAccount = {
-        payto_uri: paytoURL,
-      };
-      if (value.auth) {
-        if (value.auth.url) {
-          newValue.credit_facade_url = value.auth.url;
-        }
-        if (value.auth.type === "none") {
-          newValue.credit_facade_credentials = {
-            type: "none",
-          };
-        }
-        if (value.auth.type === "basic") {
-          newValue.credit_facade_credentials = {
-            type: "basic",
-            username: value.auth.username ?? "",
-            password: value.auth.password ?? "",
-          };
-        }
-      }
-      onChange([newValue, ...accounts] as any);
-    }
-    valueHandler(defaultTarget);
-  }, [value]);
+  // const submit = useCallback((): void => {
+  //   // const accounts: MerchantBackend.BankAccounts.AccountAddDetails[] = paytos;
+  //   // const alreadyExists =
+  //   //   accounts.findIndex((x) => x.payto_uri === paytoURL) !== -1;
+  //   // if (!alreadyExists) {
+  //   const newValue: MerchantBackend.BankAccounts.AccountAddDetails = {
+  //     payto_uri: paytoURL,
+  //   };
+  //   if (value.auth) {
+  //     if (value.auth.url) {
+  //       newValue.credit_facade_url = value.auth.url;
+  //     }
+  //     if (value.auth.type === "none") {
+  //       newValue.credit_facade_credentials = {
+  //         type: "none",
+  //       };
+  //     }
+  //     if (value.auth.type === "basic") {
+  //       newValue.credit_facade_credentials = {
+  //         type: "basic",
+  //         username: value.auth.username ?? "",
+  //         password: value.auth.password ?? "",
+  //       };
+  //     }
+  //   }
+  //   onChange(newValue as any);
+  //   // }
+  //   // valueHandler(defaultTarget);
+  // }, [value]);
 
   //FIXME: translating plural singular
   return (
@@ -299,11 +269,11 @@ export function InputPaytoForm<T>({
         name="tax"
         errors={errors}
         object={value}
-        valueHandler={valueHandler}
+        valueHandler={setValue}
       >
         <InputSelector<Entity>
           name="target"
-          label={i18n.str`Target type`}
+          label={i18n.str`Account type`}
           tooltip={i18n.str`Method to use for wire transfer`}
           values={targets}
           toStr={(v) => (v === noTargetValue ? i18n.str`Choose one...` : v)}
@@ -400,150 +370,15 @@ export function InputPaytoForm<T>({
         {value.target !== noTargetValue && (
           <Fragment>
             <Input
-              name="options.receiver-name"
+              name="params.receiver-name"
               label={i18n.str`Name`}
               tooltip={i18n.str`Bank account owner's name.`}
             />
-            <InputWithAddon
-              name="auth.url"
-              label={i18n.str`Account info URL`}
-              help="https://bank.com"
-              expand
-              tooltip={i18n.str`From where the merchant can download information about incoming wire transfers to this account`}
-            />
-            <InputSelector
-              name="auth.type"
-              label={i18n.str`Auth type`}
-              tooltip={i18n.str`Choose the authentication type for the account info URL`}
-              values={accountAuthType}
-              toStr={(str) => {
-                // if (str === "unset") {
-                //   return "Without change";
-                // }
-                if (str === "none") return "Without authentication";
-                return "Username and password";
-              }}
-            />
-            {value.auth?.type === "basic" ? (
-              <Fragment>
-                <Input
-                  name="auth.username"
-                  label={i18n.str`Username`}
-                  tooltip={i18n.str`Username to access the account information.`}
-                />
-                <Input
-                  name="auth.password"
-                  inputType="password"
-                  label={i18n.str`Password`}
-                  tooltip={i18n.str`Password to access the account information.`}
-                />
-                <Input
-                  name="auth.repeat"
-                  inputType="password"
-                  label={i18n.str`Repeat password`}
-                />
-              </Fragment>
-            ) : undefined}
-
-            {/* <InputWithAddon
-              name="options.credit_credentials"
-              label={i18n.str`Account info`}
-              inputType={showKey ? "text" : "password"}
-              help="From where the merchant can download information about incoming wire transfers to this account"
-              expand
-              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={
-                      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>
-              }
-            /> */}
           </Fragment>
         )}
-        {/**
-         * Show the values in the list
-         */}
-        <div class="field is-horizontal">
-          <div class="field-label is-normal" />
-          <div class="field-body" style={{ display: "block" }}>
-            {paytos.map(
-              (v: MerchantBackend.Instances.MerchantBankAccount, i: number) => (
-                <div
-                  key={i}
-                  class="tags has-addons mt-3 mb-0 mr-3"
-                  style={{ flexWrap: "nowrap" }}
-                >
-                  <span
-                    class="tag is-medium is-info mb-0"
-                    style={{ maxWidth: "90%" }}
-                  >
-                    {v.payto_uri}
-                  </span>
-                  <a
-                    class="tag is-medium is-danger is-delete mb-0"
-                    onClick={() => {
-                      onChange(paytos.filter((f: any) => f !== v) as any);
-                    }}
-                  />
-                </div>
-              ),
-            )}
-            {!paytos.length && i18n.str`No accounts yet.`}
-            {required && (
-              <span class="icon has-text-danger is-right">
-                <i class="mdi mdi-alert" />
-              </span>
-            )}
-          </div>
-        </div>
 
-        {value.target !== noTargetValue && (
-          <div class="buttons is-right mt-5">
-            <button
-              class="button is-info"
-              data-tooltip={i18n.str`add tax to the tax list`}
-              disabled={hasErrors}
-              onClick={submit}
-            >
-              <i18n.Translate>Add</i18n.Translate>
-            </button>
-          </div>
-        )}
       </FormProvider>
     </InputGroup>
   );
 }
 
-function tryUrl(s: string): URL | undefined {
-  try {
-    return new URL(s);
-  } catch (e) {
-    return undefined;
-  }
-}
-- 
cgit v1.2.3

