From bf03157b6570af6804032e206a3c60a3c7e030f3 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 6 May 2024 12:47:45 -0300 Subject: add required validation --- packages/aml-backoffice-ui/src/forms.json | 336 +++++++++++---------- packages/aml-backoffice-ui/src/hooks/form.ts | 291 +++++------------- .../aml-backoffice-ui/src/pages/CaseUpdate.tsx | 51 ++-- packages/aml-backoffice-ui/src/pages/Cases.tsx | 2 +- .../aml-backoffice-ui/src/pages/CreateAccount.tsx | 3 +- packages/aml-backoffice-ui/src/utils/converter.ts | 119 -------- 6 files changed, 284 insertions(+), 518 deletions(-) delete mode 100644 packages/aml-backoffice-ui/src/utils/converter.ts (limited to 'packages/aml-backoffice-ui/src') 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[k] extends AmountJson ? TranslatedString : T[k] extends AbsoluteTime - ? TranslatedString - : T[k] extends TalerExchangeApi.AmlState ? TranslatedString - : FormErrors; + : T[k] extends TalerExchangeApi.AmlState + ? TranslatedString + : FormErrors; }; export type FormStatus = @@ -85,17 +88,19 @@ function constructFormHandler( updateForm: (d: RecursivePartial>) => void, errors: FormErrors | undefined, ): FormHandler { - 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(form as any, path, undefined) - const currentError = getValueDeeper(errors as any, path, undefined) + const currentValue = getValueDeeper(form as any, path, undefined); + const currentError = getValueDeeper( + errors as any, + path, + undefined, + ); const field: UIFieldHandler = { error: currentError, value: currentValue, @@ -103,14 +108,12 @@ function constructFormHandler( state: {}, //FIXME: add the state of the field (hidden, ) }; - return setValueDeeper(handleForm, path, field) - + return setValueDeeper(handleForm, path, field); }, {} as FormHandler); return handler; } - /** * FIXME: Consider sending this to web-utils * @@ -135,7 +138,7 @@ export function useFormState( interface Tree extends Record | T> {} -function getValueDeeper( +export function getValueDeeper( object: Tree | undefined, names: string[], notFoundValue?: T, @@ -146,225 +149,79 @@ function getValueDeeper( return getValueDeeper(object, rest, notFoundValue); } if (object === undefined) { - return notFoundValue + return notFoundValue; } return getValueDeeper(object[head] as Tree, rest, notFoundValue); } -function getValueDeeper2( - object: Record, - 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, - 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, -): 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 { + const shape: Array = []; + 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 { + const shape: Array = []; + 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( + errors: FormErrors | undefined, + form: object, + fields: Array, +): FormErrors | undefined { + let result: FormErrors | 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 = []; + const requiredFields: Array = []; + 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(shape, initial, (st) => { - const errors = undefinedIfEmpty>({ + const partialErrors = undefinedIfEmpty>({ 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 | 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 ( @@ -207,7 +216,12 @@ export function CaseUpdate({
@@ -269,4 +283,3 @@ export function SelectForm({ account }: { account: string }) { ); } - 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(obj: T): T | undefined { +export function undefinedIfEmpty(obj: T): T | undefined { + if (obj === undefined) return undefined; return Object.keys(obj).some( (k) => (obj as Record)[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 - */ - -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 { - 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 { - 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 { - 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!; -} -- cgit v1.2.3