gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: array input fixed


From: gnunet
Subject: [taler-wallet-core] branch master updated: array input fixed
Date: Mon, 09 Dec 2024 18:14:05 +0100

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

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

The following commit(s) were added to refs/heads/master by this push:
     new 178ce8d6b array input fixed
178ce8d6b is described below

commit 178ce8d6b8bdf5f3139d14c91058e2e54044bffd
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Mon Dec 9 14:13:27 2024 -0300

    array input fixed
---
 packages/kyc-ui/src/forms/accept-tos.ts            |   2 +-
 packages/kyc-ui/src/hooks/form.ts                  |   1 -
 packages/kyc-ui/src/pages/FillForm.tsx             |  72 ++++-----
 packages/web-util/src/forms/InputArray.tsx         | 164 +++++++++++++++++----
 packages/web-util/src/forms/InputChoiceStacked.tsx |   3 +-
 packages/web-util/src/forms/InputLine.tsx          |  11 +-
 packages/web-util/src/forms/forms.ts               |  15 +-
 .../form.ts => web-util/src/hooks/useForm.ts}      |  77 +++++++---
 8 files changed, 241 insertions(+), 104 deletions(-)

diff --git a/packages/kyc-ui/src/forms/accept-tos.ts 
b/packages/kyc-ui/src/forms/accept-tos.ts
index 0545efd8c..e7cbb95b8 100644
--- a/packages/kyc-ui/src/forms/accept-tos.ts
+++ b/packages/kyc-ui/src/forms/accept-tos.ts
@@ -37,7 +37,7 @@ export const acceptTos = (i18n: InternationalizationAPI, 
context?: any): DoubleC
         } : undefined,
         {
           type: "choiceHorizontal",
-          id: "asd" as UIHandlerId,
+          id: "ACCEPTED_TERMS_OF_SERVICE" as UIHandlerId,
           required: true,
           label: i18n.str`Do you accept terms of service`,
           choices: [
diff --git a/packages/kyc-ui/src/hooks/form.ts 
b/packages/kyc-ui/src/hooks/form.ts
index c2f5b41b5..452deabaf 100644
--- a/packages/kyc-ui/src/hooks/form.ts
+++ b/packages/kyc-ui/src/hooks/form.ts
@@ -92,7 +92,6 @@ function constructFormHandler<T>(
     const path = fieldId.split(".");
 
     function updater(newValue: unknown) {
-      console.log("----",path, newValue)
       updateForm(setValueDeeper(form, path, newValue));
     }
 
diff --git a/packages/kyc-ui/src/pages/FillForm.tsx 
b/packages/kyc-ui/src/pages/FillForm.tsx
index 760603318..4d3f00e9e 100644
--- a/packages/kyc-ui/src/pages/FillForm.tsx
+++ b/packages/kyc-ui/src/pages/FillForm.tsx
@@ -45,6 +45,7 @@ import {
 } from "../hooks/form.js";
 import { undefinedIfEmpty } from "./Start.js";
 import { useUiFormsContext } from "../context/ui-forms.js";
+import { usePreferences } from "../context/preferences.js";
 
 type Props = {
   token: AccessToken;
@@ -78,6 +79,7 @@ export function FillForm({
   const { config, lib } = useExchangeApiContext();
   // const { forms } = useUiFormsContext();
   const [notification, withErrorHandler] = useLocalNotificationHandler();
+  const [preferences] = usePreferences();
 
   const customForm =
     requirement.context && "form" in requirement.context
@@ -183,44 +185,48 @@ export function FillForm({
     <div class="rounded-lg bg-white px-5 py-6 shadow m-4">
       <LocalNotificationBanner notification={notification} />
       <div class="space-y-10 divide-y -mt-5 divide-gray-900/10">
-          {theForm.config.design.map((section, i) => {
-            if (!section) return <Fragment />;
-            return (
-              <div
-                key={i}
-                class="grid grid-cols-1 gap-x-8 gap-y-8 pt-5 md:grid-cols-3"
-              >
-                <div class="px-4 sm:px-0">
-                  <h2 class="text-base font-semibold leading-7 text-gray-900">
-                    {section.title}
-                  </h2>
-                  {section.description && (
-                    <p class="mt-1 text-sm leading-6 text-gray-600">
-                      {section.description}
-                    </p>
-                  )}
-                </div>
-                <div class="bg-white shadow-sm ring-1 ring-gray-900/5 
rounded-md md:col-span-2">
-                  <div class="p-3">
-                    <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 
sm:grid-cols-6">
-                      <RenderAllFieldsByUiConfig
-                        key={i}
-                        fields={convertUiField(
-                          i18n,
-                          section.fields,
-                          form,
-                          getConverterById,
-                        )}
-                      />
-                    </div>
+        {theForm.config.design.map((section, i) => {
+          if (!section) return <Fragment />;
+          return (
+            <div
+              key={i}
+              class="grid grid-cols-1 gap-x-8 gap-y-8 pt-5 md:grid-cols-3"
+            >
+              <div class="px-4 sm:px-0">
+                <h2 class="text-base font-semibold leading-7 text-gray-900">
+                  {section.title}
+                </h2>
+                {section.description && (
+                  <p class="mt-1 text-sm leading-6 text-gray-600">
+                    {section.description}
+                  </p>
+                )}
+              </div>
+              <div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-md 
md:col-span-2">
+                <div class="p-3">
+                  <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 
sm:grid-cols-6">
+                    <RenderAllFieldsByUiConfig
+                      key={i}
+                      fields={convertUiField(
+                        i18n,
+                        section.fields,
+                        form,
+                        getConverterById,
+                      )}
+                    />
                   </div>
                 </div>
               </div>
-            );
-          })}
+            </div>
+          );
+        })}
       </div>
 
-      <pre>{JSON.stringify(state.result, undefined, 2)}</pre>
+      {preferences.showDebugInfo ? (
+        <pre>{JSON.stringify(state.result, undefined, 2)}</pre>
+      ) : (
+        <Fragment />
+      )}
       <div class="mt-6 flex items-center justify-end gap-x-6">
         <button
           onClick={onComplete}
diff --git a/packages/web-util/src/forms/InputArray.tsx 
b/packages/web-util/src/forms/InputArray.tsx
index 5db185561..60a13afae 100644
--- a/packages/web-util/src/forms/InputArray.tsx
+++ b/packages/web-util/src/forms/InputArray.tsx
@@ -1,11 +1,23 @@
 import { TranslatedString } from "@gnu-taler/taler-util";
 import { Fragment, VNode, h } from "preact";
-import { useState } from "preact/hooks";
+import { useEffect, useState } from "preact/hooks";
 import { FormProvider, UIFormProps } from "./FormProvider.js";
 import { LabelWithTooltipMaybeRequired } from "./InputLine.js";
-import { RenderAllFieldsByUiConfig, UIFormField } from "./forms.js";
+import {
+  convertUiField,
+  RenderAllFieldsByUiConfig,
+  UIFormField,
+} from "./forms.js";
 import { useField } from "./useField.js";
 import { UIFormElementConfig, UIHandlerId } from "./ui-form.js";
+import {
+  FormErrors,
+  undefinedIfEmpty,
+  useFormState,
+  useFormStateFromConfig,
+  validateRequiredFields,
+} from "../hooks/useForm.js";
+import { getConverterById, useTranslationContext } from "../index.browser.js";
 
 function Option({
   label,
@@ -80,9 +92,30 @@ export function noHandlerPropsAndNoContextForField(
   );
 }
 
-function getShapeFromFields(
-  fields: UIFormElementConfig[],
-): Array<UIHandlerId> {
+// function getRequiredFields(fields: UIFormField[]): Array<UIHandlerId> {
+//   const shape: Array<UIHandlerId> = [];
+//   fields.forEach((field) => {
+//     if ("name" in field.properties) {
+//       // FIXME: this should be a validation when loading the form
+//       // consistency check
+//       if (shape.indexOf(field.properties.name) !== -1) {
+//         throw Error(`already present: ${field.properties.name}`);
+//       }
+//       if (!field.properties.required) {
+//         return;
+//       }
+//       shape.push(field.properties.name);
+//     } else if (field.type === "group") {
+//       Array.prototype.push.apply(
+//         shape,
+//         getRequiredFields(field.properties.fields),
+//       );
+//     }
+//   });
+//   return shape;
+// }
+
+function getRequiredFields(fields: UIFormElementConfig[]): Array<UIHandlerId> {
   const shape: Array<UIHandlerId> = [];
   fields.forEach((field) => {
     if ("id" in field) {
@@ -91,32 +124,66 @@ function getShapeFromFields(
       if (shape.indexOf(field.id) !== -1) {
         throw Error(`already present: ${field.id}`);
       }
+      if (!field.required) {
+        return;
+      }
       shape.push(field.id);
     } else if (field.type === "group") {
-      Array.prototype.push.apply(
-        shape,
-        getShapeFromFields(field.fields),
-      );
+      Array.prototype.push.apply(shape, getRequiredFields(field.fields));
     }
   });
   return shape;
 }
 
+// function getShapeFromFields(fields: UIFormField[]): Array<UIHandlerId> {
+//   const shape: Array<UIHandlerId> = [];
+//   fields.forEach((field) => {
+//     if ("name" in field.properties) {
+//       // FIXME: this should be a validation when loading the form
+//       // consistency check
+//       if (shape.indexOf(field.properties.name) !== -1) {
+//         throw Error(`already present: ${field.properties.name}`);
+//       }
+//       shape.push(field.properties.name);
+//     } else if (field.type === "group") {
+//       Array.prototype.push.apply(
+//         shape,
+//         getShapeFromFields(field.properties.fields),
+//       );
+//     }
+//   });
+//   return shape;
+// }
+
+function getShapeFromFields(fields: UIFormElementConfig[]): Array<UIHandlerId> 
{
+  const shape: Array<UIHandlerId> = [];
+  fields.forEach((field) => {
+    if ("id" in field) {
+      // FIXME: this should be a validation when loading the form
+      // consistency check
+      if (shape.indexOf(field.id) !== -1) {
+        throw Error(`already present: ${field.id}`);
+      }
+      shape.push(field.id);
+    } else if (field.type === "group") {
+      Array.prototype.push.apply(shape, getShapeFromFields(field.fields));
+    }
+  });
+  return shape;
+}
+
+type FormType = {};
+
 export function InputArray<T extends object, K extends keyof T>(
   props: {
-    fields: UIFormField[];
+    fields: UIFormElementConfig[];
     labelField: string;
   } & UIFormProps<T, K>,
 ): VNode {
   const { fields, labelField, name, label, required, tooltip } = props;
-  // const { value, onChange, state } = useField<T, K>(name);
-  //FIXME: remove deprecated
-  const fieldCtx = useField<T, K>(props.name);
-  if (!props.handler && !fieldCtx) {
-    throw Error("");
-  }
+
   const { value, onChange, state } =
-    props.handler ?? fieldCtx ?? 
noHandlerPropsAndNoContextForField(props.name);
+    props.handler ?? noHandlerPropsAndNoContextForField(props.name);
 
   const list = (value ?? []) as Array<Record<string, string | undefined>>;
   const [selectedIndex, setSelectedIndex] = useState<number | undefined>(
@@ -125,15 +192,48 @@ export function InputArray<T extends object, K extends 
keyof T>(
   const selected =
     selectedIndex === undefined ? undefined : list[selectedIndex];
 
-  const shape: Array<UIHandlerId> = [];
-  const requiredFields: Array<UIHandlerId> = [];
+  // const shape: Array<UIHandlerId> = [];
+  // const requiredFields: Array<UIHandlerId> = [];
+  // Array.prototype.push.apply(shape, getShapeFromFields(fields));
+  // Array.prototype.push.apply(requiredFields, getRequiredFields(fields));
 
-    Array.prototype.push.apply(shape, getShapeFromFields(fields));
-    Array.prototype.push.apply(
-      requiredFields,
-      getRequiredFields(fields),
-    );
-  
+  const [form, formState] = useFormStateFromConfig<FormType>(
+    fields,
+    selected ?? {},
+  );
+  // const [form, formState] = useFormState<FormType>(
+  //   shape,
+  //   selected ?? {},
+  //   (st) => {
+  //     const partialErrors = undefinedIfEmpty<FormErrors<FormType>>({});
+
+  //     const errors = undefinedIfEmpty<FormErrors<FormType> | undefined>(
+  //       validateRequiredFields(partialErrors, st, requiredFields),
+  //     );
+
+  //     if (errors === undefined) {
+  //       return {
+  //         status: "ok",
+  //         result: st as any,
+  //         errors: undefined,
+  //       };
+  //     }
+
+  //     return {
+  //       status: "fail",
+  //       result: st as any,
+  //       errors,
+  //     };
+  //   },
+  // );
+
+  useEffect(() => {
+    if (selectedIndex === undefined) return;
+    const newValue = [...list];
+    newValue.splice(selectedIndex, 1, formState.result);
+    onChange(newValue as any);
+  }, [formState.result, selectedIndex]);
+  const { i18n } = useTranslationContext();
 
   return (
     <div class="sm:col-span-6">
@@ -210,11 +310,13 @@ export function InputArray<T extends object, K extends 
keyof T>(
           //     onChange(newValue as any);
           //   }}
           // >
-            <div class="px-4 py-6">
-              <div class="grid grid-cols-1 gap-y-8 ">
-                <RenderAllFieldsByUiConfig fields={fields}  />
-              </div>
+          <div class="px-4 py-6">
+            <div class="grid grid-cols-1 gap-y-8 ">
+              <RenderAllFieldsByUiConfig
+                fields={convertUiField(i18n, fields, form, getConverterById)}
+              />
             </div>
+          </div>
           // </FormProvider>
         )}
         {selectedIndex !== undefined && (
@@ -226,7 +328,7 @@ export function InputArray<T extends object, K extends 
keyof T>(
               }}
               class="block px-3 py-2 text-sm font-semibold leading-6 
text-gray-900"
             >
-              Close
+              <i18n.Translate>Close</i18n.Translate>
             </button>
 
             <button
@@ -240,7 +342,7 @@ export function InputArray<T extends object, K extends 
keyof T>(
               }}
               class="block rounded-md bg-red-600 px-3 py-2 text-center text-sm 
 text-white shadow-sm hover:bg-red-500 "
             >
-              Remove
+              <i18n.Translate>Remove</i18n.Translate>
             </button>
           </div>
         )}
diff --git a/packages/web-util/src/forms/InputChoiceStacked.tsx 
b/packages/web-util/src/forms/InputChoiceStacked.tsx
index 1928f4365..34b06ec0c 100644
--- a/packages/web-util/src/forms/InputChoiceStacked.tsx
+++ b/packages/web-util/src/forms/InputChoiceStacked.tsx
@@ -30,9 +30,8 @@ export function InputChoiceStacked<T extends object, K 
extends keyof T>(
   } = props;
 
   //FIXME: remove deprecated
-  const fieldCtx = useField<T, K>(props.name);
   const { value, onChange, state } =
-    props.handler ?? fieldCtx ?? 
noHandlerPropsAndNoContextForField(props.name);
+    props.handler ?? noHandlerPropsAndNoContextForField(props.name);
 
   if (state.hidden) {
     return <Fragment />;
diff --git a/packages/web-util/src/forms/InputLine.tsx 
b/packages/web-util/src/forms/InputLine.tsx
index db5e00551..7eceea88e 100644
--- a/packages/web-util/src/forms/InputLine.tsx
+++ b/packages/web-util/src/forms/InputLine.tsx
@@ -161,23 +161,14 @@ export function InputLine<T extends object, K extends 
keyof T>(
   props: { type: InputType } & UIFormProps<T, K>,
 ): VNode {
   const { name, placeholder, before, after, converter, type, disabled } = 
props;
-  //FIXME: remove deprecated
-  const fieldCtx = useField<T, K>(props.name);
+
   const { value, onChange, state, error } =
     props.handler ?? noHandlerPropsAndNoContextForField(props.name);
 
-  // const [text, setText] = useState("");
   const fromString: (s: string) => any =
     converter?.fromStringUI ?? defaultFromString;
   const toString: (s: any) => string = converter?.toStringUI ?? 
defaultToString;
 
-  // useEffect(() => {
-  //   const newValue = toString(value);
-  //   if (newValue) {
-  //     setText(newValue);
-  //   }
-  // }, [value]);
-
   if (state.hidden) return <div />;
 
   let clazz =
diff --git a/packages/web-util/src/forms/forms.ts 
b/packages/web-util/src/forms/forms.ts
index ce9b9e258..0172ea54d 100644
--- a/packages/web-util/src/forms/forms.ts
+++ b/packages/web-util/src/forms/forms.ts
@@ -212,12 +212,13 @@ export function convertUiField(
             ...converBaseFieldsProps(i18n_, config),
             ...converInputFieldsProps(form, config, getConverterById),
             labelField: config.labelFieldId,
-            fields: convertUiField(
-              i18n_,
-              config.fields,
-              (form as any)[config.id].value ?? {},
-              getConverterById,
-            ),
+            fields: config.fields,
+            // convertUiField(
+            //   i18n_,
+            //   config.fields,
+            //   (form as any)[config.id].value ?? {},
+            //   getConverterById,
+            // ),
           },
         } as UIFormField;
       }
@@ -349,7 +350,7 @@ function converInputFieldsProps(
   getConverterById: GetConverterById,
 ) {
   const names = p.id.split(".");
-  console.log("NAMES", names, getValueDeeper2(form, names), form !== undefined)
+  console.log("NAMES", names, getValueDeeper2(form, names), form)
   return {
     converter: getConverterById(p.converterId, p),
     handler: getValueDeeper2(form, names),
diff --git a/packages/kyc-ui/src/hooks/form.ts 
b/packages/web-util/src/hooks/useForm.ts
similarity index 73%
copy from packages/kyc-ui/src/hooks/form.ts
copy to packages/web-util/src/hooks/useForm.ts
index c2f5b41b5..aa277db06 100644
--- a/packages/kyc-ui/src/hooks/form.ts
+++ b/packages/web-util/src/hooks/useForm.ts
@@ -26,13 +26,6 @@ import {
   UIHandlerId,
 } from "@gnu-taler/web-util/browser";
 import { useState } from "preact/hooks";
-import { undefinedIfEmpty } from "../pages/Start.js";
-
-// export type UIField = {
-//   value: string | undefined;
-//   onUpdate: (s: string) => void;
-//   error: TranslatedString | undefined;
-// };
 
 export type FormHandler<T> = {
   [k in keyof T]?: T[k] extends string
@@ -92,7 +85,6 @@ function constructFormHandler<T>(
     const path = fieldId.split(".");
 
     function updater(newValue: unknown) {
-      console.log("----",path, newValue)
       updateForm(setValueDeeper(form, path, newValue));
     }
 
@@ -115,9 +107,48 @@ function constructFormHandler<T>(
   return handler;
 }
 
+export function useFormStateFromConfig<T>(
+  fields: Array<UIFormElementConfig>,
+  defaultValue: RecursivePartial<FormValues<T>>,
+  check?: (f: RecursivePartial<FormValues<T>>) => FormStatus<T>,
+): [FormHandler<T>, FormStatus<T>] {
+  const shape: Array<UIHandlerId> = [];
+  const requiredFields: Array<UIHandlerId> = [];
+  Array.prototype.push.apply(shape, getShapeFromFields(fields));
+  Array.prototype.push.apply(requiredFields, getRequiredFields(fields));
+
+  const [form, updateForm] =
+    useState<RecursivePartial<FormValues<T>>>(defaultValue);
+
+  function defaultCheckAllRequired(st: RecursivePartial<FormValues<T>>) {
+    const partialErrors = undefinedIfEmpty<FormErrors<T>>({});
+
+    const errors = undefinedIfEmpty<FormErrors<T> | undefined>(
+      validateRequiredFields(partialErrors, st, requiredFields),
+    );
+
+    if (errors !== undefined) {
+      return {
+        status: "fail" as const,
+        result: st as any,
+        errors,
+      };
+    }
+
+    return undefined;
+  }
+  // check required fields
+  const requiredCheckResult = requiredFields.length > 0 ? 
defaultCheckAllRequired(form) : undefined;
+  // verify if there is a custom check function and all required fields are ok
+  // if there no custom check return "ok"
+  const status = requiredCheckResult ?? (check ? check(form) : {status: "ok" 
as const, result: form as any, errors: undefined})
+  const handler = constructFormHandler(shape, form, updateForm, 
requiredCheckResult?.errors);
+
+  return [handler, status];
+}
+
 /**
- * FIXME: Consider sending this to web-utils
- *
+ * @deprecated use `useFormStateFromConfig`
  *
  * @param defaultValue
  * @param check
@@ -164,7 +195,21 @@ export function setValueDeeper(object: any, names: 
string[], value: any): any {
   if (object === undefined) {
     return undefinedIfEmpty({ [head]: setValueDeeper({}, rest, value) });
   }
-  return undefinedIfEmpty({ ...object, [head]: setValueDeeper(object[head] ?? 
{}, rest, value) });
+  return undefinedIfEmpty({
+    ...object,
+    [head]: setValueDeeper(object[head] ?? {}, rest, value),
+  });
+}
+
+export function undefinedIfEmpty<T extends object | undefined>(
+  obj: T,
+): T | undefined {
+  if (obj === undefined) return undefined;
+  return Object.keys(obj).some(
+    (k) => (obj as Record<string, T>)[k] !== undefined,
+  )
+    ? obj
+    : undefined;
 }
 
 export function getShapeFromFields(
@@ -180,10 +225,7 @@ export function getShapeFromFields(
       }
       shape.push(field.id);
     } else if (field.type === "group") {
-      Array.prototype.push.apply(
-        shape,
-        getShapeFromFields(field.fields),
-      );
+      Array.prototype.push.apply(shape, getShapeFromFields(field.fields));
     }
   });
   return shape;
@@ -205,10 +247,7 @@ export function getRequiredFields(
       }
       shape.push(field.id);
     } else if (field.type === "group") {
-      Array.prototype.push.apply(
-        shape,
-        getRequiredFields(field.fields),
-      );
+      Array.prototype.push.apply(shape, getRequiredFields(field.fields));
     }
   });
   return shape;

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