aboutsummaryrefslogtreecommitdiff
path: root/packages/aml-backoffice-ui
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2024-05-06 12:47:45 -0300
committerSebastian <sebasjm@gmail.com>2024-05-06 12:47:45 -0300
commitbf03157b6570af6804032e206a3c60a3c7e030f3 (patch)
tree4d2b9ec0f0919703783bfe9ad40e2c02b27c2501 /packages/aml-backoffice-ui
parent35fee72ef3d75b7a9681353ab7a1ca5bacff150e (diff)
downloadwallet-core-bf03157b6570af6804032e206a3c60a3c7e030f3.tar.xz
add required validation
Diffstat (limited to 'packages/aml-backoffice-ui')
-rw-r--r--packages/aml-backoffice-ui/src/forms.json336
-rw-r--r--packages/aml-backoffice-ui/src/hooks/form.ts291
-rw-r--r--packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx51
-rw-r--r--packages/aml-backoffice-ui/src/pages/Cases.tsx2
-rw-r--r--packages/aml-backoffice-ui/src/pages/CreateAccount.tsx3
-rw-r--r--packages/aml-backoffice-ui/src/utils/converter.ts119
6 files changed, 284 insertions, 518 deletions
diff --git a/packages/aml-backoffice-ui/src/forms.json b/packages/aml-backoffice-ui/src/forms.json
index de0da601c..ed556307b 100644
--- a/packages/aml-backoffice-ui/src/forms.json
+++ b/packages/aml-backoffice-ui/src/forms.json
@@ -17,6 +17,7 @@
"name": "customerType",
"id": ".customerType",
"label": "Type of customer",
+ "help": "Select one and complete the next form",
"required": true,
"choices": [
{
@@ -47,173 +48,186 @@
"label": "Full name",
"required": true
}
- }
+ },
+ {
+ "type": "text",
+ "properties": {
+ "name": "naturalCustomer.address",
+ "id": ".naturalCustomer.address",
+ "label": "Residential address",
+ "required": true
+ }
+ },
+ {
+ "type": "integer",
+ "properties": {
+ "name": "naturalCustomer.telephone",
+ "id": ".naturalCustomer.telephone",
+ "label": "Telephone"
+ }
+ },
+ {
+ "type": "text",
+ "properties": {
+ "name": "naturalCustomer.email",
+ "id": ".naturalCustomer.email",
+ "label": "E-mail"
+ }
+ },
+ {
+ "type": "absoluteTime",
+ "properties": {
+ "pattern": "dd/MM/yyyy",
+ "name": "naturalCustomer.dateOfBirth",
+ "id": ".naturalCustomer.dateOfBirth",
+ "label": "Date of birth",
+ "required": true
+ }
+ },
+ {
+ "type": "text",
+ "properties": {
+ "name": "naturalCustomer.nationality",
+ "id": ".naturalCustomer.nationality",
+ "label": "Nationality",
+ "required": true
+ }
+ },
+ {
+ "type": "text",
+ "properties": {
+ "name": "naturalCustomer.document",
+ "id": ".naturalCustomer.document",
+ "label": "Identification document",
+ "required": true
+ }
+ },
+ {
+ "type": "file",
+ "properties": {
+ "name": "naturalCustomer.documentAttachment",
+ "id": ".naturalCustomer.documentAttachment",
+ "label": "Document attachment",
+ "required": true,
+ "maxBites": 2097152,
+ "accept": ".pdf",
+ "help": "PDF file with max size of 2 mega bytes"
+ }
+ },
+ {
+ "type": "text",
+ "properties": {
+ "name": "naturalCustomer.companyName",
+ "id": ".naturalCustomer.companyName",
+ "label": "Company name"
+ }
+ },
+ {
+ "type": "text",
+ "properties": {
+ "name": "naturalCustomer.office",
+ "id": ".naturalCustomer.office",
+ "label": "Registered office"
+ }
+ },
+ {
+ "type": "text",
+ "properties": {
+ "name": "naturalCustomer.companyDocument",
+ "id": ".naturalCustomer.companyDocument",
+ "label": "Company identification document"
+ }
+ },
+ {
+ "type": "file",
+ "properties": {
+ "name": "naturalCustomer.companyDocumentAttachment",
+ "id": ".naturalCustomer.companyDocumentAttachment",
+ "label": "Document attachment",
+ "required": true,
+ "maxBites": 2097152,
+ "accept": ".png",
+ "help": "PNG file with max size of 2 mega bytes"
+ }
+ }
]
}
},
+
{
- "type": "text",
- "properties": {
- "name": "naturalCustomer.address",
- "id": ".naturalCustomer.address",
- "label": "Residential address",
- "required": true
- }
- },
- {
- "type": "integer",
- "properties": {
- "name": "naturalCustomer.telephone",
- "id": ".naturalCustomer.telephone",
- "label": "Telephone"
- }
- },
- {
- "type": "text",
- "properties": {
- "name": "naturalCustomer.email",
- "id": ".naturalCustomer.email",
- "label": "E-mail"
- }
- },
- {
- "type": "absoluteTime",
- "properties": {
- "pattern": "dd/MM/yyyy",
- "name": "naturalCustomer.dateOfBirth",
- "id": ".naturalCustomer.dateOfBirth",
- "label": "Date of birth",
- "required": true
- }
- },
- {
- "type": "text",
- "properties": {
- "name": "naturalCustomer.nationality",
- "id": ".naturalCustomer.nationality",
- "label": "Nationality",
- "required": true
- }
- },
- {
- "type": "text",
- "properties": {
- "name": "naturalCustomer.document",
- "id": ".naturalCustomer.document",
- "label": "Identification document",
- "required": true
- }
- },
- {
- "type": "file",
- "properties": {
- "name": "naturalCustomer.documentAttachment",
- "id": ".naturalCustomer.documentAttachment",
- "label": "Document attachment",
- "required": true,
- "maxBites": 2097152,
- "accept": ".pdf",
- "help": "PDF file with max size of 2 mega bytes"
- }
- },
- {
- "type": "text",
- "properties": {
- "name": "naturalCustomer.companyName",
- "id": ".naturalCustomer.companyName",
- "label": "Company name"
- }
- },
- {
- "type": "text",
- "properties": {
- "name": "naturalCustomer.office",
- "id": ".naturalCustomer.office",
- "label": "Registered office"
- }
- },
- {
- "type": "text",
- "properties": {
- "name": "naturalCustomer.companyDocument",
- "id": ".naturalCustomer.companyDocument",
- "label": "Company identification document"
- }
- },
- {
- "type": "file",
- "properties": {
- "name": "naturalCustomer.companyDocumentAttachment",
- "id": ".naturalCustomer.companyDocumentAttachment",
- "label": "Document attachment",
- "required": true,
- "maxBites": 2097152,
- "accept": ".png",
- "help": "PNG file with max size of 2 mega bytes"
- }
- },
- {
- "type": "text",
- "properties": {
- "name": "legalCustomer.companyName",
- "id": ".legalCustomer.companyName",
- "label": "Company name",
- "required": true
- }
- },
- {
- "type": "text",
- "properties": {
- "name": "legalCustomer.domicile",
- "id": ".legalCustomer.domicile",
- "label": "Domicile",
- "required": true
- }
- },
- {
- "type": "text",
- "properties": {
- "name": "legalCustomer.contactPerson",
- "id": ".legalCustomer.contactPerson",
- "label": "Contact person"
- }
- },
- {
- "type": "text",
- "properties": {
- "name": "legalCustomer.telephone",
- "id": ".legalCustomer.telephone",
- "label": "Telephone"
- }
- },
- {
- "type": "text",
- "properties": {
- "name": "legalCustomer.email",
- "id": ".legalCustomer.email",
- "label": "E-mail"
- }
- },
- {
- "type": "text",
- "properties": {
- "name": "legalCustomer.document",
- "id": ".legalCustomer.document",
- "label": "Identification document",
- "help": "Not older than 12 month"
- }
- },
- {
- "type": "file",
+ "type": "group",
"properties": {
- "name": "legalCustomer.documentAttachment",
- "id": ".legalCustomer.documentAttachment",
- "label": "Document attachment",
- "required": true,
- "maxBites": 2097152,
- "accept": ".png",
- "help": "PNG file with max size of 2 mega bytes"
+ "label": "Natural customer form",
+ "name": "algo",
+ "id": "algo",
+ "before": "a) Country risk (nationality)",
+ "after": "a) Country risk (nationality)",
+ "fields": [
+ {
+ "type": "text",
+ "properties": {
+ "name": "legalCustomer.companyName",
+ "id": ".legalCustomer.companyName",
+ "label": "Company name",
+ "required": true
+ }
+ },
+ {
+ "type": "text",
+ "properties": {
+ "name": "legalCustomer.domicile",
+ "id": ".legalCustomer.domicile",
+ "label": "Domicile",
+ "required": true
+ }
+ },
+ {
+ "type": "text",
+ "properties": {
+ "name": "legalCustomer.contactPerson",
+ "id": ".legalCustomer.contactPerson",
+ "label": "Contact person"
+ }
+ },
+ {
+ "type": "text",
+ "properties": {
+ "name": "legalCustomer.telephone",
+ "id": ".legalCustomer.telephone",
+ "label": "Telephone"
+ }
+ },
+ {
+ "type": "text",
+ "properties": {
+ "name": "legalCustomer.email",
+ "id": ".legalCustomer.email",
+ "label": "E-mail"
+ }
+ },
+ {
+ "type": "text",
+ "properties": {
+ "name": "legalCustomer.document",
+ "id": ".legalCustomer.document",
+ "label": "Identification document",
+ "help": "Not older than 12 month"
+ }
+ },
+ {
+ "type": "file",
+ "properties": {
+ "name": "legalCustomer.documentAttachment",
+ "id": ".legalCustomer.documentAttachment",
+ "label": "Document attachment",
+ "required": true,
+ "maxBites": 2097152,
+ "accept": ".png",
+ "help": "PNG file with max size of 2 mega bytes"
+ }
+ }
+ ]
}
}
]
diff --git a/packages/aml-backoffice-ui/src/hooks/form.ts b/packages/aml-backoffice-ui/src/hooks/form.ts
index 033d1d950..e9194d86d 100644
--- a/packages/aml-backoffice-ui/src/hooks/form.ts
+++ b/packages/aml-backoffice-ui/src/hooks/form.ts
@@ -19,11 +19,14 @@ import {
AmountJson,
TalerExchangeApi,
TranslatedString,
- assertUnreachable,
} from "@gnu-taler/taler-util";
-import { Addon, InternationalizationAPI, UIFieldBaseDescription, UIFieldHandler, UIFormField, UIFormFieldBaseConfig, UIFormFieldConfig, UIHandlerId } from "@gnu-taler/web-util/browser";
+import {
+ UIFieldHandler,
+ UIFormFieldConfig,
+ UIHandlerId,
+} from "@gnu-taler/web-util/browser";
import { useState } from "preact/hooks";
-import { getConverterById } from "../utils/converter.js";
+import { undefinedIfEmpty } from "../pages/CreateAccount.js";
// export type UIField = {
// value: string | undefined;
@@ -61,10 +64,10 @@ export type FormErrors<T> = {
: T[k] extends AmountJson
? TranslatedString
: T[k] extends AbsoluteTime
- ? TranslatedString
- : T[k] extends TalerExchangeApi.AmlState
? TranslatedString
- : FormErrors<T[k]>;
+ : T[k] extends TalerExchangeApi.AmlState
+ ? TranslatedString
+ : FormErrors<T[k]>;
};
export type FormStatus<T> =
@@ -85,17 +88,19 @@ function constructFormHandler<T>(
updateForm: (d: RecursivePartial<FormValues<T>>) => void,
errors: FormErrors<T> | undefined,
): FormHandler<T> {
-
const handler = shape.reduce((handleForm, fieldId) => {
+ const path = fieldId.split(".");
- const path = fieldId.split(".")
-
function updater(newValue: unknown) {
updateForm(setValueDeeper(form, path, newValue));
}
- const currentValue = getValueDeeper<string>(form as any, path, undefined)
- const currentError = getValueDeeper<TranslatedString>(errors as any, path, undefined)
+ const currentValue = getValueDeeper<string>(form as any, path, undefined);
+ const currentError = getValueDeeper<TranslatedString>(
+ errors as any,
+ path,
+ undefined,
+ );
const field: UIFieldHandler = {
error: currentError,
value: currentValue,
@@ -103,14 +108,12 @@ function constructFormHandler<T>(
state: {}, //FIXME: add the state of the field (hidden, )
};
- return setValueDeeper(handleForm, path, field)
-
+ return setValueDeeper(handleForm, path, field);
}, {} as FormHandler<T>);
return handler;
}
-
/**
* FIXME: Consider sending this to web-utils
*
@@ -135,7 +138,7 @@ export function useFormState<T>(
interface Tree<T> extends Record<string, Tree<T> | T> {}
-function getValueDeeper<T>(
+export function getValueDeeper<T>(
object: Tree<T> | undefined,
names: string[],
notFoundValue?: T,
@@ -146,225 +149,79 @@ function getValueDeeper<T>(
return getValueDeeper(object, rest, notFoundValue);
}
if (object === undefined) {
- return notFoundValue
+ return notFoundValue;
}
return getValueDeeper(object[head] as Tree<T>, rest, notFoundValue);
}
-function getValueDeeper2(
- object: Record<string, any>,
- names: string[],
-): UIFieldHandler {
- if (names.length === 0) return object as UIFieldHandler;
- const [head, ...rest] = names;
- if (!head) {
- return getValueDeeper2(object, rest);
- }
- if (object === undefined) {
- throw Error("handler not found");
- }
- return getValueDeeper2(object[head], rest);
-}
-
-
-function setValueDeeper(object: any, names: string[], value: any): any {
+export function setValueDeeper(object: any, names: string[], value: any): any {
if (names.length === 0) return value;
const [head, ...rest] = names;
if (!head) {
return setValueDeeper(object, rest, value);
}
if (object === undefined) {
- return { [head]: setValueDeeper({}, rest, value) };
+ return undefinedIfEmpty({ [head]: setValueDeeper({}, rest, value) });
}
- return { ...object, [head]: setValueDeeper(object[head] ?? {}, rest, value) };
-}
-
-function getAddonById(_id: string | undefined): Addon {
- return undefined!;
-}
-
-
-function converInputFieldsProps(
- form: FormHandler<unknown>,
- p: UIFormFieldBaseConfig,
-) {
- return {
- converter: getConverterById(p.converterId, p),
- handler: getValueDeeper2(form, p.id.split(".")),
- name: p.name,
- required: p.required,
- disabled: p.disabled,
- help: p.help,
- placeholder: p.placeholder,
- tooltip: p.tooltip,
- label: p.label as TranslatedString,
- };
+ return undefinedIfEmpty({ ...object, [head]: setValueDeeper(object[head] ?? {}, rest, value) });
}
-function converBaseFieldsProps(
- i18n_: InternationalizationAPI,
- p: UIFieldBaseDescription,
-) {
- return {
- after: getAddonById(p.addonAfterId),
- before: getAddonById(p.addonBeforeId),
- hidden: p.hidden,
- name: p.name,
- help: i18n_.str`${p.help}`,
- label: i18n_.str`${p.label}`,
- tooltip: i18n_.str`${p.tooltip}`,
- };
-}
-
-export function convertUiField(
- i18n_: InternationalizationAPI,
- fieldConfig: UIFormFieldConfig[],
- form: FormHandler<unknown>,
-): UIFormField[] {
- return fieldConfig.map((config) => {
- // NON input fields
- switch (config.type) {
- case "caption": {
- const resp: UIFormField = {
- type: config.type,
- properties: converBaseFieldsProps(i18n_, config.properties),
- };
- return resp;
- }
- case "group": {
- const resp: UIFormField = {
- type: config.type,
- properties: {
- ...converBaseFieldsProps(i18n_, config.properties),
- fields: convertUiField(i18n_, config.properties.fields, form),
- },
- };
- return resp;
+export function getShapeFromFields(
+ fields: UIFormFieldConfig[],
+): Array<UIHandlerId> {
+ const shape: Array<UIHandlerId> = [];
+ fields.forEach((field) => {
+ if ("id" in field.properties) {
+ // FIXME: this should be a validation when loading the form
+ // consistency check
+ if (shape.indexOf(field.properties.id) !== -1) {
+ throw Error(`already present: ${field.properties.id}`);
}
+ shape.push(field.properties.id);
+ } else if (field.type === "group") {
+ Array.prototype.push.apply(
+ shape,
+ getShapeFromFields(field.properties.fields),
+ );
}
- // Input Fields
- switch (config.type) {
- case "array": {
- return {
- type: "array",
- properties: {
- ...converBaseFieldsProps(i18n_, config.properties),
- ...converInputFieldsProps(form, config.properties),
- labelField: config.properties.labelFieldId,
- fields: convertUiField(i18n_, config.properties.fields, form),
- },
- } as UIFormField;
- }
- case "absoluteTime": {
- return {
- type: "absoluteTime",
- properties: {
- ...converBaseFieldsProps(i18n_, config.properties),
- ...converInputFieldsProps(form, config.properties),
- },
- } as UIFormField;
- }
- case "amount": {
- return {
- type: "amount",
- properties: {
- ...converBaseFieldsProps(i18n_, config.properties),
- ...converInputFieldsProps(form, config.properties),
- },
- } as UIFormField;
- }
- case "choiceHorizontal": {
- return {
- type: "choiceHorizontal",
- properties: {
- ...converBaseFieldsProps(i18n_, config.properties),
- ...converInputFieldsProps(form, config.properties),
- choices: config.properties.choices,
- },
- } as UIFormField;
- }
- case "choiceStacked": {
- return {
- type: "choiceStacked",
- properties: {
- ...converBaseFieldsProps(i18n_, config.properties),
- ...converInputFieldsProps(form, config.properties),
- choices: config.properties.choices,
-
- },
- }as UIFormField;
- }
- case "file":{
- console.log("ASDASD", config.properties.accept)
- return {
- type: "file",
- properties: {
- ...converBaseFieldsProps(i18n_, config.properties),
- ...converInputFieldsProps(form, config.properties),
- accept: config.properties.accept,
- maxBites: config.properties.maxBytes,
- },
- } as UIFormField;
- }
- case "integer":{
- return {
- type: "integer",
- properties: {
- ...converBaseFieldsProps(i18n_, config.properties),
- ...converInputFieldsProps(form, config.properties),
- },
- } as UIFormField;
- }
- case "selectMultiple":{
- return {
- type: "selectMultiple",
- properties: {
- ...converBaseFieldsProps(i18n_, config.properties),
- ...converInputFieldsProps(form, config.properties),
- choices: config.properties.choices,
- },
- } as UIFormField;
- }
- case "selectOne": {
- return {
- type: "selectOne",
- properties: {
- ...converBaseFieldsProps(i18n_, config.properties),
- ...converInputFieldsProps(form, config.properties),
- choices: config.properties.choices,
- },
- } as UIFormField;
- }
- case "text": {
- return {
- type: "text",
- properties: {
- ...converBaseFieldsProps(i18n_, config.properties),
- ...converInputFieldsProps(form, config.properties),
- },
- } as UIFormField;
- }
- case "textArea": {
- return {
- type: "text",
- properties: {
- ...converBaseFieldsProps(i18n_, config.properties),
- ...converInputFieldsProps(form, config.properties),
- },
- } as UIFormField;
- }
- case "toggle": {
- return {
- type: "toggle",
- properties: {
- ...converBaseFieldsProps(i18n_, config.properties),
- ...converInputFieldsProps(form, config.properties),
- },
- } as UIFormField;
+ });
+ return shape;
+}
+
+export function getRequiredFields(
+ fields: UIFormFieldConfig[],
+): Array<UIHandlerId> {
+ const shape: Array<UIHandlerId> = [];
+ fields.forEach((field) => {
+ if ("id" in field.properties) {
+ // FIXME: this should be a validation when loading the form
+ // consistency check
+ if (shape.indexOf(field.properties.id) !== -1) {
+ throw Error(`already present: ${field.properties.id}`);
}
- default: {
- assertUnreachable(config);
+ if (!field.properties.required) {
+ return;
}
+ shape.push(field.properties.id);
+ } else if (field.type === "group") {
+ Array.prototype.push.apply(
+ shape,
+ getRequiredFields(field.properties.fields),
+ );
}
});
+ return shape;
+}
+export function validateRequiredFields<FormType>(
+ errors: FormErrors<FormType> | undefined,
+ form: object,
+ fields: Array<UIHandlerId>,
+): FormErrors<FormType> | undefined {
+ let result: FormErrors<FormType> | undefined = errors;
+ fields.forEach((f) => {
+ const path = f.split(".");
+ const v = getValueDeeper(form as any, path);
+ result = setValueDeeper(result, path, !v ? "required" : undefined);
+ });
+ return result;
}
diff --git a/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx b/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx
index 2f3ee054d..712a1fed9 100644
--- a/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx
+++ b/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx
@@ -29,17 +29,23 @@ import {
LocalNotificationBanner,
RenderAllFieldsByUiConfig,
UIHandlerId,
+ convertUiField,
+ getConverterById,
useExchangeApiContext,
useLocalNotificationHandler,
- useTranslationContext
+ useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { privatePages } from "../Routing.js";
-import {
- useUiFormsContext
-} from "../context/ui-forms.js";
+import { useUiFormsContext } from "../context/ui-forms.js";
import { preloadedForms } from "../forms/index.js";
-import { FormErrors, convertUiField, useFormState } from "../hooks/form.js";
+import {
+ FormErrors,
+ getRequiredFields,
+ getShapeFromFields,
+ useFormState,
+ validateRequiredFields,
+} from "../hooks/form.js";
import { useOfficer } from "../hooks/officer.js";
import { Justification } from "./CaseDetails.js";
import { undefinedIfEmpty } from "./CreateAccount.js";
@@ -101,25 +107,27 @@ export function CaseUpdate({
}
const shape: Array<UIHandlerId> = [];
+ const requiredFields: Array<UIHandlerId> = [];
+
theForm.config.design.forEach((section) => {
- section.fields.forEach((field) => {
- if ("id" in field.properties) {
- //FIXME: this should be a validation
- if (shape.indexOf(field.properties.id) !== -1) {
- throw Error(`already present: ${field.properties.id}`)
- }
- shape.push(field.properties.id);
- }
- });
+ Array.prototype.push.apply(shape, getShapeFromFields(section.fields));
+ Array.prototype.push.apply(
+ requiredFields,
+ getRequiredFields(section.fields),
+ );
});
const [form, state] = useFormState<FormType>(shape, initial, (st) => {
- const errors = undefinedIfEmpty<FormErrors<FormType>>({
+ const partialErrors = undefinedIfEmpty<FormErrors<FormType>>({
state: st.state === undefined ? i18n.str`required` : undefined,
threshold: !st.threshold ? i18n.str`required` : undefined,
when: !st.when ? i18n.str`required` : undefined,
- comment: !st.comment ? i18n.str`required` : undefined,
});
+
+ const errors = undefinedIfEmpty<FormErrors<FormType> | undefined>(
+ validateRequiredFields(partialErrors, st, requiredFields),
+ );
+
if (errors === undefined) {
return {
status: "ok",
@@ -127,6 +135,7 @@ export function CaseUpdate({
errors: undefined,
};
}
+
return {
status: "fail",
result: st as any,
@@ -136,6 +145,7 @@ export function CaseUpdate({
const validatedForm = state.status !== "ok" ? undefined : state.result;
+ console.log(state.errors);
const submitHandler =
validatedForm === undefined
? undefined
@@ -180,7 +190,6 @@ export function CaseUpdate({
}
},
);
-
return (
<Fragment>
<LocalNotificationBanner notification={notification} />
@@ -207,7 +216,12 @@ export function CaseUpdate({
<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)}
+ fields={convertUiField(
+ i18n,
+ section.fields,
+ form,
+ getConverterById,
+ )}
/>
</div>
</div>
@@ -269,4 +283,3 @@ export function SelectForm({ account }: { account: string }) {
</div>
);
}
-
diff --git a/packages/aml-backoffice-ui/src/pages/Cases.tsx b/packages/aml-backoffice-ui/src/pages/Cases.tsx
index 7b848487a..3a7fc89f2 100644
--- a/packages/aml-backoffice-ui/src/pages/Cases.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Cases.tsx
@@ -25,6 +25,7 @@ import {
InputChoiceHorizontal,
Loading,
UIHandlerId,
+ amlStateConverter,
useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
@@ -35,7 +36,6 @@ import { privatePages } from "../Routing.js";
import { FormErrors, RecursivePartial, useFormState } from "../hooks/form.js";
import { undefinedIfEmpty } from "./CreateAccount.js";
import { Officer } from "./Officer.js";
-import { amlStateConverter } from "../utils/converter.js";
type FormType = {
state: TalerExchangeApi.AmlState;
diff --git a/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx b/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx
index f4904933b..87310aa27 100644
--- a/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx
+++ b/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx
@@ -89,7 +89,8 @@ function createFormValidator(
};
}
-export function undefinedIfEmpty<T extends object>(obj: T): T | undefined {
+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,
)
diff --git a/packages/aml-backoffice-ui/src/utils/converter.ts b/packages/aml-backoffice-ui/src/utils/converter.ts
deleted file mode 100644
index 187a5412f..000000000
--- a/packages/aml-backoffice-ui/src/utils/converter.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022-2024 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import {
- AbsoluteTime,
- AmountJson,
- Amounts,
- TalerExchangeApi,
-} from "@gnu-taler/taler-util";
-import { StringConverter } from "@gnu-taler/web-util/browser";
-import { format, parse } from "date-fns";
-
-export const amlStateConverter = {
- toStringUI: stringifyAmlState,
- fromStringUI: parseAmlState,
-};
-
-function stringifyAmlState(s: TalerExchangeApi.AmlState | undefined): string {
- if (s === undefined) return "";
- switch (s) {
- case TalerExchangeApi.AmlState.normal:
- return "normal";
- case TalerExchangeApi.AmlState.pending:
- return "pending";
- case TalerExchangeApi.AmlState.frozen:
- return "frozen";
- }
-}
-
-function parseAmlState(s: string | undefined): TalerExchangeApi.AmlState {
- switch (s) {
- case "normal":
- return TalerExchangeApi.AmlState.normal;
- case "pending":
- return TalerExchangeApi.AmlState.pending;
- case "frozen":
- return TalerExchangeApi.AmlState.frozen;
- default:
- throw Error(`unknown AML state: ${s}`);
- }
-}
-
-function amountConverter(config: any): StringConverter<AmountJson> {
- const currency = config["currency"];
- if (!currency || typeof currency !== "string") {
- throw Error(`amount converter needs a currency`);
- }
- return {
- fromStringUI(v: string | undefined): AmountJson {
- // FIXME: requires currency
- return Amounts.parse(`${currency}:${v}`) ?? Amounts.zeroOfCurrency(currency);
- },
- toStringUI(v: unknown): string {
- return v === undefined ? "" : Amounts.stringifyValue(v as AmountJson);
- },
- };
-}
-
-function absTimeConverter(config: any): StringConverter<AbsoluteTime> {
- const pattern = config["pattern"];
- if (!pattern || typeof pattern !== "string") {
- throw Error(`absTime converter needs a pattern`);
- }
- return {
- fromStringUI(v: string | undefined): AbsoluteTime {
- if (v === undefined) {
- return AbsoluteTime.never();
- }
- try {
- const time = parse(v, pattern, new Date());
- return AbsoluteTime.fromMilliseconds(time.getTime());
- } catch(e) {
- return AbsoluteTime.never();
- }
- },
- toStringUI(v: unknown): string {
- if (v === undefined) return "";
- const d = v as AbsoluteTime;
- if (d.t_ms === "never") return "never";
- try {
- return format(d.t_ms, pattern)
- } catch (e) {
- return ""
- }
- },
- };
-}
-
-export function getConverterById(
- id: string | undefined,
- config: unknown,
-): StringConverter<unknown> {
- if (id === "Taler.AbsoluteTime") {
- // @ts-expect-error check this
- return absTimeConverter(config);
- }
- if (id === "Taler.Amount") {
- // @ts-expect-error check this
- return amountConverter(config);
- }
- if (id === "TalerExchangeApi.AmlState") {
- // @ts-expect-error check this
- return amlStateConverter;
- }
- return undefined!;
-}