[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.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-wallet-core] branch master updated: array input fixed,
gnunet <=