From 22709ff4e2918a8d0e528539d11d761381920b45 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 29 Apr 2024 17:23:04 -0300 Subject: use exchange api type and start using ui_fields --- .../aml-backoffice-ui/src/forms/declaration.ts | 5 +- packages/aml-backoffice-ui/src/forms/simplest.ts | 16 +- packages/aml-backoffice-ui/src/hooks/form.ts | 46 +++-- packages/aml-backoffice-ui/src/hooks/officer.ts | 12 +- packages/aml-backoffice-ui/src/hooks/useCases.ts | 6 +- .../src/pages/AntiMoneyLaunderingForm.stories.tsx | 104 ------------ .../src/pages/AntiMoneyLaunderingForm.tsx | 185 --------------------- .../aml-backoffice-ui/src/pages/CaseDetails.tsx | 107 ++++++++++-- .../aml-backoffice-ui/src/pages/CaseUpdate.tsx | 180 +++++++++++++------- .../aml-backoffice-ui/src/pages/Cases.stories.tsx | 7 +- packages/aml-backoffice-ui/src/pages/Cases.tsx | 125 ++++++++------ .../aml-backoffice-ui/src/pages/CreateAccount.tsx | 99 ++++------- .../src/pages/HandleAccountNotReady.tsx | 19 +-- .../src/pages/ShowConsolidated.stories.tsx | 14 +- .../src/pages/ShowConsolidated.tsx | 14 +- .../aml-backoffice-ui/src/pages/UnlockAccount.tsx | 145 ++++++++++------ .../aml-backoffice-ui/src/pages/index.stories.ts | 1 - packages/aml-backoffice-ui/src/utils/converter.ts | 34 +++- packages/aml-backoffice-ui/src/utils/types.ts | 124 -------------- packages/web-util/src/forms/DefaultForm.tsx | 4 +- packages/web-util/src/forms/forms.ts | 2 - 21 files changed, 507 insertions(+), 742 deletions(-) delete mode 100644 packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.stories.tsx delete mode 100644 packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx delete mode 100644 packages/aml-backoffice-ui/src/utils/types.ts (limited to 'packages') diff --git a/packages/aml-backoffice-ui/src/forms/declaration.ts b/packages/aml-backoffice-ui/src/forms/declaration.ts index fb7b8f334..c467f537b 100644 --- a/packages/aml-backoffice-ui/src/forms/declaration.ts +++ b/packages/aml-backoffice-ui/src/forms/declaration.ts @@ -14,12 +14,11 @@ GNU Taler; see the file COPYING. If not, see */ -import type { AmountJson, TranslatedString } from "@gnu-taler/taler-util"; +import type { AmountJson, TalerExchangeApi, TranslatedString } from "@gnu-taler/taler-util"; import type { FlexibleForm, InternationalizationAPI, } from "@gnu-taler/web-util/browser"; -import { AmlExchangeBackend } from "../utils/types.js"; /** * import entry point without hard reference. @@ -32,7 +31,7 @@ import { AmlExchangeBackend } from "../utils/types.js"; */ export interface BaseForm { - state: AmlExchangeBackend.AmlState; + state: TalerExchangeApi.AmlState; threshold: AmountJson; } diff --git a/packages/aml-backoffice-ui/src/forms/simplest.ts b/packages/aml-backoffice-ui/src/forms/simplest.ts index bd512546d..6455b6f41 100644 --- a/packages/aml-backoffice-ui/src/forms/simplest.ts +++ b/packages/aml-backoffice-ui/src/forms/simplest.ts @@ -13,13 +13,13 @@ You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ -import type { - TranslatedString +import { + TalerExchangeApi, + type TranslatedString } from "@gnu-taler/taler-util"; import type { DoubleColumnFormSection, FlexibleForm, FormState, InternationalizationAPI } from "@gnu-taler/web-util/browser"; import { amlStateConverter } from "../utils/converter.js"; -import { AmlExchangeBackend } from "../utils/types.js"; import { BaseForm } from "./declaration.js"; export const v1 = (i18n: InternationalizationAPI) => (current: BaseForm): FlexibleForm => ({ @@ -44,10 +44,9 @@ export const v1 = (i18n: InternationalizationAPI) => (current: BaseForm): Flexib return { comment: { help: ((v.comment?.length ?? 0) > 100 ? "keep it short" : "") as TranslatedString, - }, threshold: { - disabled: v.state === AmlExchangeBackend.AmlState.frozen, + disabled: v.state === TalerExchangeApi.AmlState.frozen, }, }; }, @@ -71,18 +70,17 @@ export function resolutionSection(current: BaseForm, i18n: InternationalizationA props: { name: "state", label: i18n.str`New state`, - converter: amlStateConverter, choices: [ { - value: AmlExchangeBackend.AmlState.frozen, + value: TalerExchangeApi.AmlState.frozen, label: i18n.str`Frozen`, }, { - value: AmlExchangeBackend.AmlState.pending, + value: TalerExchangeApi.AmlState.pending, label: i18n.str`Pending`, }, { - value: AmlExchangeBackend.AmlState.normal, + value: TalerExchangeApi.AmlState.normal, label: i18n.str`Normal`, }, ], diff --git a/packages/aml-backoffice-ui/src/hooks/form.ts b/packages/aml-backoffice-ui/src/hooks/form.ts index e3d97db8c..e14e29819 100644 --- a/packages/aml-backoffice-ui/src/hooks/form.ts +++ b/packages/aml-backoffice-ui/src/hooks/form.ts @@ -14,29 +14,32 @@ GNU Taler; see the file COPYING. If not, see */ -import { AmountJson, TranslatedString } from "@gnu-taler/taler-util"; +import { + AmountJson, + TalerExchangeApi, + TranslatedString, +} from "@gnu-taler/taler-util"; import { useState } from "preact/hooks"; +import { UIField } from "@gnu-taler/web-util/browser"; -export type UIField = { - value: string | undefined; - onUpdate: (s: string) => void; - error: TranslatedString | undefined; -}; +// export type UIField = { +// value: string | undefined; +// onUpdate: (s: string) => void; +// error: TranslatedString | undefined; +// }; type FormHandler = { [k in keyof T]?: T[k] extends string ? UIField : T[k] extends AmountJson ? UIField - : FormHandler; + : T[k] extends TalerExchangeApi.AmlState + ? UIField + : FormHandler; }; export type FormValues = { - [k in keyof T]: T[k] extends string - ? string | undefined - : T[k] extends AmountJson - ? string | undefined - : FormValues; + [k in keyof T]: T[k] extends string ? string | undefined : FormValues; }; export type RecursivePartial = { @@ -44,7 +47,9 @@ export type RecursivePartial = { ? string : T[k] extends AmountJson ? AmountJson - : RecursivePartial; + : T[k] extends TalerExchangeApi.AmlState + ? TalerExchangeApi.AmlState + : RecursivePartial; }; export type FormErrors = { @@ -52,7 +57,9 @@ export type FormErrors = { ? TranslatedString : T[k] extends AmountJson ? TranslatedString - : FormErrors; + : T[k] extends TalerExchangeApi.AmlState + ? TranslatedString + : FormErrors; }; export type FormStatus = @@ -76,10 +83,15 @@ function constructFormHandler( const handler = keys.reduce((prev, fieldName) => { const currentValue: unknown = form[fieldName]; - const currentError: unknown = errors ? errors[fieldName] : undefined; + const currentError: unknown = + errors !== undefined ? errors[fieldName] : undefined; function updater(newValue: unknown) { updateForm({ ...form, [fieldName]: newValue }); } + /** + * There is no clear way to know if this object is a custom field + * or a group of fields + */ if (typeof currentValue === "object") { // @ts-expect-error FIXME better typing const group = constructFormHandler(currentValue, updater, currentError); @@ -87,12 +99,14 @@ function constructFormHandler( prev[fieldName] = group; return prev; } + const field: UIField = { // @ts-expect-error FIXME better typing error: currentError, // @ts-expect-error FIXME better typing value: currentValue, - onUpdate: updater, + onChange: updater, + state: {}, }; // @ts-expect-error FIXME better typing prev[fieldName] = field; diff --git a/packages/aml-backoffice-ui/src/hooks/officer.ts b/packages/aml-backoffice-ui/src/hooks/officer.ts index dabe866d3..1bb73b8fc 100644 --- a/packages/aml-backoffice-ui/src/hooks/officer.ts +++ b/packages/aml-backoffice-ui/src/hooks/officer.ts @@ -66,14 +66,14 @@ interface OfficerNotFound { } interface OfficerLocked { state: "locked"; - forget: () => void; - tryUnlock: (password: string) => Promise; + forget: () => OperationOk; + tryUnlock: (password: string) => Promise>; } interface OfficerReady { state: "ready"; account: OfficerAccount; - forget: () => void; - lock: () => void; + forget: () => OperationOk; + lock: () => OperationOk; } const OFFICER_KEY = buildStorageKey("officer", codecForOfficer()); @@ -133,6 +133,7 @@ export function useOfficer(): OfficerState { state: "locked", forget: () => { officerStorage.reset(); + return opFixedSuccess(undefined) }, tryUnlock: async (pwd: string) => { const ac = await unlockOfficerAccount(officer.account, pwd); @@ -141,6 +142,7 @@ export function useOfficer(): OfficerState { id: ac.id, strKey: encodeCrock(ac.signingKey), }); + return opFixedSuccess(undefined) }, }; } @@ -150,10 +152,12 @@ export function useOfficer(): OfficerState { account, lock: () => { accountStorage.reset(); + return opFixedSuccess(undefined) }, forget: () => { officerStorage.reset(); accountStorage.reset(); + return opFixedSuccess(undefined) }, }; } diff --git a/packages/aml-backoffice-ui/src/hooks/useCases.ts b/packages/aml-backoffice-ui/src/hooks/useCases.ts index 59d1c9001..d3a1c1018 100644 --- a/packages/aml-backoffice-ui/src/hooks/useCases.ts +++ b/packages/aml-backoffice-ui/src/hooks/useCases.ts @@ -19,11 +19,11 @@ import { useState } from "preact/hooks"; import { OfficerAccount, OperationOk, + TalerExchangeApi, TalerExchangeResultByMethod, TalerHttpError, } from "@gnu-taler/taler-util"; import _useSWR, { SWRHook } from "swr"; -import { AmlExchangeBackend } from "../utils/types.js"; import { useOfficer } from "./officer.js"; import { useExchangeApiContext } from "@gnu-taler/web-util/browser"; const useSWR = _useSWR as unknown as SWRHook; @@ -39,7 +39,7 @@ export const PAGINATED_LIST_REQUEST = PAGINATED_LIST_SIZE + 1; * @param args * @returns */ -export function useCases(state: AmlExchangeBackend.AmlState) { +export function useCases(state: TalerExchangeApi.AmlState) { const officer = useOfficer(); const session = officer.state === "ready" ? officer.account : undefined; const { @@ -50,7 +50,7 @@ export function useCases(state: AmlExchangeBackend.AmlState) { async function fetcher([officer, state, offset]: [ OfficerAccount, - AmlExchangeBackend.AmlState, + TalerExchangeApi.AmlState, string | undefined, ]) { return await api.getDecisionsByState(officer, state, { diff --git a/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.stories.tsx b/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.stories.tsx deleted file mode 100644 index 0c82a4a0e..000000000 --- a/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.stories.tsx +++ /dev/null @@ -1,104 +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 - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import * as tests from "@gnu-taler/web-util/testing"; -import { - AntiMoneyLaunderingForm as TestedComponent, -} from "./AntiMoneyLaunderingForm.js"; - -export default { - title: "aml form", -}; - -export const SimpleComment = tests.createExample(TestedComponent, { - account: "the_account", - formId: "simple_comment", - onSubmit: async (justification, newState, newThreshold) => { - alert(JSON.stringify({ justification, newState, newThreshold }, undefined, 2)) - } -}); - -export const Identification = tests.createExample(TestedComponent, { - account: "the_account", - formId: "902.1e", - onSubmit: async (justification, newState, newThreshold) => { - alert(JSON.stringify({ justification, newState, newThreshold }, undefined, 2)) - } -}); - -export const OperationalLegalEntity = tests.createExample(TestedComponent, { - account: "the_account", - formId: "902.11e", - - onSubmit: async (justification, newState, newThreshold) => { - alert(JSON.stringify({ justification, newState, newThreshold }, undefined, 2)) - } -}); -export const Foundations = tests.createExample(TestedComponent, { - account: "the_account", - formId: "902.12e", - - onSubmit: async (justification, newState, newThreshold) => { - alert(JSON.stringify({ justification, newState, newThreshold }, undefined, 2)) - } -}); -export const DelcarationOfTrusts = tests.createExample(TestedComponent, { - account: "the_account", - formId: "902.13e", - - onSubmit: async (justification, newState, newThreshold) => { - alert(JSON.stringify({ justification, newState, newThreshold }, undefined, 2)) - } -}); - -export const InformationOnLifeInsurance = tests.createExample(TestedComponent, { - account: "the_account", - formId: "902.15e", - - onSubmit: async (justification, newState, newThreshold) => { - alert(JSON.stringify({ justification, newState, newThreshold }, undefined, 2)) - } -}); -export const DeclarationOfBeneficialOwner = tests.createExample(TestedComponent, { - account: "the_account", - formId: "902.9e", - - onSubmit: async (justification, newState, newThreshold) => { - alert(JSON.stringify({ justification, newState, newThreshold }, undefined, 2)) - } -}); -export const CustomerProfile = tests.createExample(TestedComponent, { - account: "the_account", - formId: "902.5e", - - onSubmit: async (justification, newState, newThreshold) => { - alert(JSON.stringify({ justification, newState, newThreshold }, undefined, 2)) - } -}); -export const RiskProfile = tests.createExample(TestedComponent, { - account: "the_account", - formId: "902.4e", - - onSubmit: async (justification, newState, newThreshold) => { - alert(JSON.stringify({ justification, newState, newThreshold }, undefined, 2)) - } -}); - diff --git a/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx b/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx deleted file mode 100644 index db034c996..000000000 --- a/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx +++ /dev/null @@ -1,185 +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, - Codec, - OperationFail, - OperationOk, - TalerErrorDetail, - buildCodecForObject, - codecForNumber, - codecForString, - codecOptional, -} from "@gnu-taler/taler-util"; -import { - DefaultForm, - useExchangeApiContext, - useTranslationContext, -} from "@gnu-taler/web-util/browser"; -import { h } from "preact"; -import { BaseForm, FormMetadata, uiForms } from "../forms/declaration.js"; -import { AmlExchangeBackend } from "../utils/types.js"; -import { privatePages } from "../Routing.js"; - -export function AntiMoneyLaunderingForm({ - account, - formId, - onSubmit, -}: { - account: string; - formId: string; - onSubmit: ( - justification: Justification, - state: AmlExchangeBackend.AmlState, - threshold: AmountJson, - ) => Promise; -}) { - const { i18n } = useTranslationContext(); - const theForm = uiForms.forms(i18n).find((v) => v.id === formId); - if (!theForm) { - return
form with id {formId} not found
; - } - - const { config } = useExchangeApiContext(); - - const initial = { - when: AbsoluteTime.now(), - state: AmlExchangeBackend.AmlState.pending, - threshold: Amounts.zeroOfCurrency(config.currency), - }; - return ( - {}} - onSubmit={(formValue) => { - if ( - formValue.state === undefined || - formValue.threshold === undefined - ) { - return; - } - const validatedForm = formValue as BaseForm; - const st = formValue.state; - const amount = formValue.threshold; - - const justification: Justification = { - id: theForm.id, - label: theForm.label, - version: theForm.version, - value: validatedForm, - }; - - onSubmit(justification, st, amount); - }} - > -
- - Cancel - - -
-
- ); -} - -export type Justification = { - // form values - value: T; -} & Omit, "icon">, "impl">; - -export function stringifyJustification(j: Justification): string { - return JSON.stringify(j); -} - -type SimpleFormMetadata = { - version?: number; - id?: string; -}; - -export const codecForSimpleFormMetadata = (): Codec => - buildCodecForObject() - .property("id", codecOptional(codecForString())) - .property("version", codecOptional(codecForNumber())) - .build("SimpleFormMetadata"); - -type ParseJustificationFail = - | "not-json" - | "id-not-found" - | "form-not-found" - | "version-not-found"; - -export function parseJustification( - s: string, - listOfAllKnownForms: FormMetadata[], -): - | OperationOk<{ - justification: Justification; - metadata: FormMetadata; - }> - | OperationFail { - try { - const justification = JSON.parse(s); - const info = codecForSimpleFormMetadata().decode(justification); - if (!info.id) { - return { - type: "fail", - case: "id-not-found", - detail: {} as TalerErrorDetail, - }; - } - if (!info.version) { - return { - type: "fail", - case: "version-not-found", - detail: {} as TalerErrorDetail, - }; - } - const found = listOfAllKnownForms.find((f) => { - return f.id === info.id && f.version === info.version; - }); - if (!found) { - return { - type: "fail", - case: "form-not-found", - detail: {} as TalerErrorDetail, - }; - } - return { - type: "ok", - body: { - justification, - metadata: found, - }, - }; - } catch (e) { - return { - type: "fail", - case: "not-json", - detail: {} as TalerErrorDetail, - }; - } -} diff --git a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx index 576cdbbb9..e16a6a103 100644 --- a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx +++ b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx @@ -17,10 +17,19 @@ import { AbsoluteTime, AmountJson, Amounts, + Codec, HttpStatusCode, + OperationFail, + OperationOk, TalerError, + TalerErrorDetail, + TalerExchangeApi, TranslatedString, assertUnreachable, + buildCodecForObject, + codecForNumber, + codecForString, + codecOptional, } from "@gnu-taler/taler-util"; import { DefaultForm, @@ -32,15 +41,10 @@ import { import { format } from "date-fns"; import { VNode, h } from "preact"; import { useState } from "preact/hooks"; +import { privatePages } from "../Routing.js"; import { BaseForm, FormMetadata, uiForms } from "../forms/declaration.js"; import { useCaseDetails } from "../hooks/useCaseDetails.js"; -import { AmlExchangeBackend } from "../utils/types.js"; -import { - Justification, - parseJustification, -} from "./AntiMoneyLaunderingForm.js"; import { ShowConsolidated } from "./ShowConsolidated.js"; -import { privatePages } from "../Routing.js"; export type AmlEvent = | AmlFormEvent @@ -53,7 +57,7 @@ type AmlFormEvent = { title: TranslatedString; justification: Justification; metadata: FormMetadata; - state: AmlExchangeBackend.AmlState; + state: TalerExchangeApi.AmlState; threshold: AmountJson; }; type AmlFormEventError = { @@ -62,7 +66,7 @@ type AmlFormEventError = { title: TranslatedString; justification: undefined; metadata: undefined; - state: AmlExchangeBackend.AmlState; + state: TalerExchangeApi.AmlState; threshold: AmountJson; }; type KycCollectionEvent = { @@ -108,8 +112,8 @@ function titleForJustification( } export function getEventsFromAmlHistory( - aml: AmlExchangeBackend.AmlDecisionDetail[], - kyc: AmlExchangeBackend.KycDetail[], + aml: TalerExchangeApi.AmlDecisionDetail[], + kyc: TalerExchangeApi.KycDetail[], i18n: InternationalizationAPI, ): AmlEvent[] { const ae: AmlEvent[] = aml.map((a) => { @@ -242,24 +246,24 @@ export function CaseDetails({ account }: { account: string }) { function AmlStateBadge({ state, }: { - state: AmlExchangeBackend.AmlState; + state: TalerExchangeApi.AmlState; }): VNode { switch (state) { - case AmlExchangeBackend.AmlState.normal: { + case TalerExchangeApi.AmlState.normal: { return ( Normal ); } - case AmlExchangeBackend.AmlState.pending: { + case TalerExchangeApi.AmlState.pending: { return ( Pending ); } - case AmlExchangeBackend.AmlState.frozen: { + case TalerExchangeApi.AmlState.frozen: { return ( Frozen @@ -384,3 +388,78 @@ function ShowTimeline({ ); } + + +export type Justification = { + // form values + value: T; +} & Omit, "icon">, "impl">; + +type SimpleFormMetadata = { + version?: number; + id?: string; +}; + +export const codecForSimpleFormMetadata = (): Codec => + buildCodecForObject() + .property("id", codecOptional(codecForString())) + .property("version", codecOptional(codecForNumber())) + .build("SimpleFormMetadata"); + +type ParseJustificationFail = + | "not-json" + | "id-not-found" + | "form-not-found" + | "version-not-found"; + +function parseJustification( + s: string, + listOfAllKnownForms: FormMetadata[], +): + | OperationOk<{ + justification: Justification; + metadata: FormMetadata; + }> + | OperationFail { + try { + const justification = JSON.parse(s); + const info = codecForSimpleFormMetadata().decode(justification); + if (!info.id) { + return { + type: "fail", + case: "id-not-found", + detail: {} as TalerErrorDetail, + }; + } + if (!info.version) { + return { + type: "fail", + case: "version-not-found", + detail: {} as TalerErrorDetail, + }; + } + const found = listOfAllKnownForms.find((f) => { + return f.id === info.id && f.version === info.version; + }); + if (!found) { + return { + type: "fail", + case: "form-not-found", + detail: {} as TalerErrorDetail, + }; + } + return { + type: "ok", + body: { + justification, + metadata: found, + }, + }; + } catch (e) { + return { + type: "fail", + case: "not-json", + detail: {} as TalerErrorDetail, + }; + } +} diff --git a/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx b/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx index c4bff1f9f..47c8f8ab4 100644 --- a/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx +++ b/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx @@ -19,24 +19,27 @@ import { HttpStatusCode, TalerExchangeApi, TalerProtocolTimestamp, - TranslatedString, + assertUnreachable } from "@gnu-taler/taler-util"; import { + Button, LocalNotificationBanner, + RenderAllFieldsByUiConfig, useExchangeApiContext, - useLocalNotification, - useTranslationContext, + useLocalNotificationHandler, + useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { privatePages } from "../Routing.js"; -import { uiForms } from "../forms/declaration.js"; +import { BaseForm, uiForms } from "../forms/declaration.js"; +import { useFormState } from "../hooks/form.js"; import { useOfficer } from "../hooks/officer.js"; -import { AntiMoneyLaunderingForm } from "./AntiMoneyLaunderingForm.js"; import { HandleAccountNotReady } from "./HandleAccountNotReady.js"; +import { Justification } from "./CaseDetails.js"; export function CaseUpdate({ account, - type, + type: formId, }: { account: string; type: string; @@ -46,67 +49,132 @@ export function CaseUpdate({ const { lib: { exchange: api }, } = useExchangeApiContext(); - const [notification, notify, handleError] = useLocalNotification(); + + // const [notification, notify, handleError] = useLocalNotification(); + const [notification, withErrorHandler] = useLocalNotificationHandler(); + const { config } = useExchangeApiContext(); + + const initial = { + when: AbsoluteTime.now(), + state: TalerExchangeApi.AmlState.pending, + threshold: Amounts.zeroOfCurrency(config.currency), + }; if (officer.state !== "ready") { return ; } + const theForm = uiForms.forms(i18n).find((v) => v.id === formId); + if (!theForm) { + return
form with id {formId} not found
; + } - return ( - - + const [form, state] = useFormState(initial, (st) => { + return { + status: "ok", + result: st as any, + errors: undefined, + }; + }); - { - const decision: Omit = { - justification: JSON.stringify(justification), - decision_time: TalerProtocolTimestamp.now(), - h_payto: account, - new_state, - new_threshold: Amounts.stringify(new_threshold), - kyc_requirements: undefined, - }; - await handleError(async () => { - const resp = await api.addDecisionDetails( - officer.account, - decision, - ); - if (resp.type === "ok") { - window.location.href = privatePages.cases.url({}); - return; - } - switch (resp.case) { + const ff = theForm.impl(state.result as any); + + const validatedForm = state.status === "fail" ? undefined : state.result; + + const submitHandler = + validatedForm === undefined + ? undefined + : withErrorHandler( + () => { + const justification: Justification = { + id: theForm.id, + label: theForm.label, + version: theForm.version, + value: validatedForm, + }; + + const decision: Omit = + { + justification: JSON.stringify(justification), + decision_time: TalerProtocolTimestamp.now(), + h_payto: account, + new_state: justification.value.state, + new_threshold: Amounts.stringify(justification.value.threshold), + kyc_requirements: undefined, + }; + + return api.addDecisionDetails(officer.account, decision); + }, + () => { + window.location.href = privatePages.cases.url({}); + }, + (fail) => { + switch (fail.case) { case HttpStatusCode.Forbidden: case HttpStatusCode.Unauthorized: - return notify({ - type: "error", - title: i18n.str`Wrong credentials for "${officer.account}"`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - when: AbsoluteTime.now(), - }); + return i18n.str`Wrong credentials for "${officer.account}"`; case HttpStatusCode.NotFound: - return notify({ - type: "error", - title: i18n.str`Officer or account not found`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - when: AbsoluteTime.now(), - }); + return i18n.str`Officer or account not found`; case HttpStatusCode.Conflict: - return notify({ - type: "error", - title: i18n.str`Officer disabled or more recent decision was already submitted.`, - description: resp.detail.hint as TranslatedString, - debug: resp.detail, - when: AbsoluteTime.now(), - }); + return i18n.str`Officer disabled or more recent decision was already submitted.`; + default: + assertUnreachable(fail); } - }); - }} - /> + }, + ); + + // const asd = ff.design[0]?.fields[0]?.props + + return ( + + +
+ {ff.design.map((section, i) => { + if (!section) return ; + return ( +
+
+

+ {section.title} +

+ {section.description && ( +

+ {section.description} +

+ )} +
+
+
+
+ +
+
+
+
+ ); + })} +
+ +
+ + Cancel + + +
); } diff --git a/packages/aml-backoffice-ui/src/pages/Cases.stories.tsx b/packages/aml-backoffice-ui/src/pages/Cases.stories.tsx index dcbd366a4..22a6d1867 100644 --- a/packages/aml-backoffice-ui/src/pages/Cases.stories.tsx +++ b/packages/aml-backoffice-ui/src/pages/Cases.stories.tsx @@ -21,19 +21,18 @@ import * as tests from "@gnu-taler/web-util/testing"; import { CasesUI as TestedComponent } from "./Cases.js"; -import { AmountString } from "@gnu-taler/taler-util"; -import { AmlExchangeBackend } from "../utils/types.js"; +import { AmountString, TalerExchangeApi } from "@gnu-taler/taler-util"; export default { title: "cases", }; export const OneRow = tests.createExample(TestedComponent, { - filter: AmlExchangeBackend.AmlState.normal, + filter: TalerExchangeApi.AmlState.normal, onChangeFilter: () => null, records: [ { - current_state: AmlExchangeBackend.AmlState.normal, + current_state: TalerExchangeApi.AmlState.normal, h_payto: "QWEQWEQWEQWE", rowid: 1, threshold: "USD:1" as AmountString, diff --git a/packages/aml-backoffice-ui/src/pages/Cases.tsx b/packages/aml-backoffice-ui/src/pages/Cases.tsx index 6b59b2736..2e92c111e 100644 --- a/packages/aml-backoffice-ui/src/pages/Cases.tsx +++ b/packages/aml-backoffice-ui/src/pages/Cases.tsx @@ -22,19 +22,23 @@ import { import { Attention, ErrorLoading, + InputChoiceHorizontal, Loading, - createNewForm, - useTranslationContext, + useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; -import { useState } from "preact/hooks"; +import { useEffect, useState } from "preact/hooks"; import { useCases } from "../hooks/useCases.js"; import { privatePages } from "../Routing.js"; -import { amlStateConverter } from "../utils/converter.js"; -import { AmlExchangeBackend } from "../utils/types.js"; +import { FormErrors, RecursivePartial, useFormState } from "../hooks/form.js"; +import { undefinedIfEmpty } from "./CreateAccount.js"; import { Officer } from "./Officer.js"; +type FormType = { + state: TalerExchangeApi.AmlState; +}; + export function CasesUI({ records, filter, @@ -44,13 +48,45 @@ export function CasesUI({ }: { onFirstPage?: () => void; onNext?: () => void; - filter: AmlExchangeBackend.AmlState; - onChangeFilter: (f: AmlExchangeBackend.AmlState) => void; + filter: TalerExchangeApi.AmlState; + onChangeFilter: (f: TalerExchangeApi.AmlState) => void; records: TalerExchangeApi.AmlRecord[]; }): VNode { const { i18n } = useTranslationContext(); - const form = createNewForm<{ state: AmlExchangeBackend.AmlState }>(); + const [form, status] = useFormState( + { + state: filter, + }, + (state) => { + const errors = undefinedIfEmpty>({ + state: state.state === undefined ? i18n.str`required` : undefined, + }); + if (errors === undefined) { + const result: FormType = { + state: state.state!, + }; + return { + status: "ok", + result, + errors, + }; + } + const result: RecursivePartial = { + state: state.state, + }; + return { + status: "fail", + result, + errors, + }; + }, + ); + useEffect(() => { + if (status.status === "ok" && filter !== status.result.state) { + onChangeFilter(status.result.state); + } + }, [form?.state?.value]); return (
@@ -66,33 +102,25 @@ export function CasesUI({

- { - onChangeFilter(v.state ?? filter); - }} - onSubmit={(_v) => {}} - > - - + + name="state" + label={i18n.str`Filter`} + handler={form.state} + choices={[ + { + label: i18n.str`Pending`, + value: TalerExchangeApi.AmlState.pending, + }, + { + label: i18n.str`Frozen`, + value: TalerExchangeApi.AmlState.frozen, + }, + { + label: i18n.str`Normal`, + value: TalerExchangeApi.AmlState.normal, + }, + ]} + />
@@ -141,23 +169,23 @@ export function CasesUI({
- {((state: AmlExchangeBackend.AmlState): VNode => { + {((state: TalerExchangeApi.AmlState): VNode => { switch (state) { - case AmlExchangeBackend.AmlState.normal: { + case TalerExchangeApi.AmlState.normal: { return ( Normal ); } - case AmlExchangeBackend.AmlState.pending: { + case TalerExchangeApi.AmlState.pending: { return ( Pending ); } - case AmlExchangeBackend.AmlState.frozen: { + case TalerExchangeApi.AmlState.frozen: { return ( Frozen @@ -186,7 +214,7 @@ export function CasesUI({ export function Cases() { const [stateFilter, setStateFilter] = useState( - AmlExchangeBackend.AmlState.pending, + TalerExchangeApi.AmlState.pending, ); const list = useCases(stateFilter); @@ -204,12 +232,10 @@ export function Cases() { case HttpStatusCode.Forbidden: { return ( - + - This account doesnt have access. Request account activation sending your public key. + This account doesnt have access. Request account activation + sending your public key. @@ -219,10 +245,7 @@ export function Cases() { case HttpStatusCode.Unauthorized: { return ( - + This account is not allowed to perform list the cases. @@ -245,7 +268,9 @@ export function Cases() { onFirstPage={list.isFirstPage ? undefined : list.loadFirst} onNext={list.isLastPage ? undefined : list.loadNext} filter={stateFilter} - onChangeFilter={setStateFilter} + onChangeFilter={(d) => { + setStateFilter(d) + }} /> ); } diff --git a/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx b/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx index 094e78531..a8a853bc1 100644 --- a/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx +++ b/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx @@ -15,9 +15,9 @@ */ import { Button, + InputLine, InternationalizationAPI, LocalNotificationBanner, - ShowInputErrorLabel, useLocalNotificationHandler, useTranslationContext, } from "@gnu-taler/web-util/browser"; @@ -66,15 +66,23 @@ function createFormValidator( }); if (errors === undefined) { + const result: FormType = { + password: state.password!, + repeat: state.repeat!, + }; return { status: "ok", - result: state as FormType, + result, errors, }; } + const result: RecursivePartial = { + password: state.password, + repeat: state.repeat, + }; return { status: "fail", - result: state, + result, errors, }; }; @@ -88,13 +96,8 @@ export function undefinedIfEmpty(obj: T): T | undefined { : undefined; } -export function CreateAccount({ - onNewAccount, -}: { - onNewAccount: () => void; -}): VNode { +export function CreateAccount(): VNode { const { i18n } = useTranslationContext(); - // const Form = createNewForm(); const [settings] = usePreferences(); const officer = useOfficer(); @@ -113,9 +116,9 @@ export function CreateAccount({ ? undefined : withErrorHandler( async () => officer.create(form.password!.value!), - onNewAccount, + () => {}, ); - + form.password; return (
@@ -137,66 +140,24 @@ export function CreateAccount({ autoCapitalize="none" autoCorrect="off" > -
- -
- { - console.log("ASDASD", form.password?.onUpdate); - form.password?.onUpdate(e.currentTarget.value); - }} - /> - -
+
+ + label={i18n.str`Password`} + name="password" + type="password" + required + handler={form.password} + />
-
- -
- { - form.repeat?.onUpdate(e.currentTarget.value); - }} - /> - -
+
+ + label={i18n.str`Repeat password`} + name="repeat" + type="password" + required + handler={form.repeat} + />
diff --git a/packages/aml-backoffice-ui/src/pages/HandleAccountNotReady.tsx b/packages/aml-backoffice-ui/src/pages/HandleAccountNotReady.tsx index b23798172..3d6e14f22 100644 --- a/packages/aml-backoffice-ui/src/pages/HandleAccountNotReady.tsx +++ b/packages/aml-backoffice-ui/src/pages/HandleAccountNotReady.tsx @@ -25,26 +25,11 @@ export function HandleAccountNotReady({ officer: OfficerNotReady; }): VNode { if (officer.state === "not-found") { - return ( - { - officer.create(password); - }} - /> - ); + return ; } if (officer.state === "locked") { - return ( - { - officer.forget(); - }} - onAccountUnlocked={async (pwd) => { - await officer.tryUnlock(pwd); - }} - /> - ); + return ; } assertUnreachable(officer); } diff --git a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.stories.tsx b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.stories.tsx index 1cb50efd2..11b25575b 100644 --- a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.stories.tsx +++ b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.stories.tsx @@ -19,7 +19,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { AbsoluteTime, Duration, TranslatedString } from "@gnu-taler/taler-util"; +import { AbsoluteTime, AmountString, Duration, TranslatedString } from "@gnu-taler/taler-util"; import { InternationalizationAPI } from "@gnu-taler/web-util/browser"; import * as tests from "@gnu-taler/web-util/testing"; import { getEventsFromAmlHistory } from "./CaseDetails.js"; @@ -48,7 +48,7 @@ export const WithSomeEvents = tests.createExample(TestedComponent, { { "decider_pub": "JD70N2XZ8FZKB7C146ZWR6XBDCS4Z84PJKJMPB73PMJ2B1X35ZFG", "justification": "{\"index\":0,\"name\":\"Simple comment\",\"value\":{\"fullName\":\"loggedIn_user_fullname\",\"when\":{\"t_ms\":1700207199558},\"state\":1,\"threshold\":{\"currency\":\"STATER\",\"fraction\":0,\"value\":0},\"comment\":\"test\"}}", - "new_threshold": "STATER:0", + "new_threshold": "STATER:0" as AmountString, "new_state": 1, "decision_time": { "t_s": 1700208199 @@ -57,7 +57,7 @@ export const WithSomeEvents = tests.createExample(TestedComponent, { { "decider_pub": "JD70N2XZ8FZKB7C146ZWR6XBDCS4Z84PJKJMPB73PMJ2B1X35ZFG", "justification": "{\"index\":0,\"name\":\"Simple comment\",\"value\":{\"fullName\":\"loggedIn_user_fullname\",\"when\":{\"t_ms\":1700207199558},\"state\":1,\"threshold\":{\"currency\":\"STATER\",\"fraction\":0,\"value\":0},\"comment\":\"test\"}}", - "new_threshold": "STATER:0", + "new_threshold": "STATER:0" as AmountString, "new_state": 1, "decision_time": { "t_s": 1700208211 @@ -66,7 +66,7 @@ export const WithSomeEvents = tests.createExample(TestedComponent, { { "decider_pub": "JD70N2XZ8FZKB7C146ZWR6XBDCS4Z84PJKJMPB73PMJ2B1X35ZFG", "justification": "{\"index\":0,\"name\":\"Simple comment\",\"value\":{\"fullName\":\"loggedIn_user_fullname\",\"when\":{\"t_ms\":1700207199558},\"state\":1,\"threshold\":{\"currency\":\"STATER\",\"fraction\":0,\"value\":0},\"comment\":\"test\"}}", - "new_threshold": "STATER:0", + "new_threshold": "STATER:0" as AmountString, "new_state": 1, "decision_time": { "t_s": 1700208220 @@ -75,7 +75,7 @@ export const WithSomeEvents = tests.createExample(TestedComponent, { { "decider_pub": "JD70N2XZ8FZKB7C146ZWR6XBDCS4Z84PJKJMPB73PMJ2B1X35ZFG", "justification": "{\"index\":4,\"name\":\"Declaration for trusts (902.13e)\",\"value\":{\"fullName\":\"loggedIn_user_fullname\",\"when\":{\"t_ms\":1700208362854},\"state\":1,\"threshold\":{\"currency\":\"STATER\",\"fraction\":0,\"value\":0},\"contractingPartner\":\"f\",\"knownAs\":\"a\",\"trust\":{\"name\":\"b\",\"type\":\"discretionary\",\"revocability\":\"irrevocable\"}}}", - "new_threshold": "STATER:0", + "new_threshold": "STATER:0" as AmountString, "new_state": 1, "decision_time": { "t_s": 1700208385 @@ -84,7 +84,7 @@ export const WithSomeEvents = tests.createExample(TestedComponent, { { "decider_pub": "6CD3J8XSKWQPFFDJY4SP4RK2D7T7WW7JRJDTXHNZY7YKGXDCE2QG", "justification": "{\"id\":\"simple_comment\",\"label\":\"Simple comment\",\"version\":1,\"value\":{\"when\":{\"t_ms\":1700488420810},\"state\":1,\"threshold\":{\"currency\":\"STATER\",\"fraction\":0,\"value\":0},\"comment\":\"qwe\"}}", - "new_threshold": "STATER:0", + "new_threshold": "STATER:0" as AmountString, "new_state": 1, "decision_time": { "t_s": 1700488423 @@ -93,7 +93,7 @@ export const WithSomeEvents = tests.createExample(TestedComponent, { { "decider_pub": "6CD3J8XSKWQPFFDJY4SP4RK2D7T7WW7JRJDTXHNZY7YKGXDCE2QG", "justification": "{\"id\":\"simple_comment\",\"label\":\"Simple comment\",\"version\":1,\"value\":{\"when\":{\"t_ms\":1700488671251},\"state\":1,\"threshold\":{\"currency\":\"STATER\",\"fraction\":0,\"value\":0},\"comment\":\"asd asd asd \"}}", - "new_threshold": "STATER:0", + "new_threshold": "STATER:0" as AmountString, "new_state": 1, "decision_time": { "t_s": 1700488677 diff --git a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx index c1f7e02cb..1115414c0 100644 --- a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx +++ b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx @@ -16,6 +16,7 @@ import { AbsoluteTime, AmountJson, + TalerExchangeApi, TranslatedString, } from "@gnu-taler/taler-util"; import { @@ -26,8 +27,6 @@ import { } from "@gnu-taler/web-util/browser"; import { format } from "date-fns"; import { Fragment, VNode, h } from "preact"; -import { amlStateConverter } from "../utils/converter.js"; -import { AmlExchangeBackend } from "../utils/types.js"; import { AmlEvent } from "./CaseDetails.js"; export function ShowConsolidated({ @@ -73,19 +72,18 @@ export function ShowConsolidated({ props: { label: i18n.str`State`, name: "aml.state", - converter: amlStateConverter, choices: [ { label: i18n.str`Frozen`, - value: AmlExchangeBackend.AmlState.frozen, + value: TalerExchangeApi.AmlState.frozen, }, { label: i18n.str`Pending`, - value: AmlExchangeBackend.AmlState.pending, + value: TalerExchangeApi.AmlState.pending, }, { label: i18n.str`Normal`, - value: AmlExchangeBackend.AmlState.normal, + value: TalerExchangeApi.AmlState.normal, }, ], }, @@ -135,7 +133,7 @@ export function ShowConsolidated({ interface Consolidated { aml: { - state: AmlExchangeBackend.AmlState; + state: TalerExchangeApi.AmlState; threshold: AmountJson; since: AbsoluteTime; }; @@ -154,7 +152,7 @@ function getConsolidated( ): Consolidated { const initial: Consolidated = { aml: { - state: AmlExchangeBackend.AmlState.normal, + state: TalerExchangeApi.AmlState.normal, threshold: { currency: "ARS", value: 1000, diff --git a/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx b/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx index de634c9e0..9552f2b0c 100644 --- a/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx +++ b/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx @@ -13,81 +13,116 @@ You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ -import { TranslatedString, UnwrapKeyError } from "@gnu-taler/taler-util"; -import { createNewForm, notifyError, notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { + Button, + InputLine, + LocalNotificationBanner, + useLocalNotificationHandler, + useTranslationContext +} from "@gnu-taler/web-util/browser"; import { VNode, h } from "preact"; +import { FormErrors, useFormState } from "../hooks/form.js"; +import { useOfficer } from "../hooks/officer.js"; +import { undefinedIfEmpty } from "./CreateAccount.js"; -export function UnlockAccount({ - onAccountUnlocked, - onRemoveAccount, -}: { - onAccountUnlocked: (password: string) => Promise; - onRemoveAccount: () => void; -}): VNode { - const { i18n } = useTranslationContext() - const Form = createNewForm<{ - password: string; - }>(); +type FormType = { + password: string; +}; + +export function UnlockAccount(): VNode { + const { i18n } = useTranslationContext(); + + const officer = useOfficer(); + const [notification, withErrorHandler] = useLocalNotificationHandler(); + + const [form, status] = useFormState( + { + password: undefined, + }, + (state) => { + const errors = undefinedIfEmpty>({ + password: !state.password ? i18n.str`required` : undefined, + }); + if (errors === undefined) { + return { + status: "ok", + result: state as FormType, + errors, + }; + } + return { + status: "fail", + result: state, + errors, + }; + }, + ); + + const unlockHandler = + status.status === "fail" || officer.state !== "locked" + ? undefined + : withErrorHandler( + async () => officer.tryUnlock(form.password!.value!), + () => {}, + ); + + const forgetHandler = + status.status === "fail" || officer.state !== "locked" + ? undefined + : withErrorHandler( + async () => officer.forget(), + () => {}, + ); return (
+ +

Account locked

- Your account is normally locked anytime you reload. To unlock type - your password again. + + Your account is normally locked anytime you reload. To unlock type + your password again. +

- { - try { - await onAccountUnlocked(v.password!); - notifyInfo(i18n.str`Account unlocked`); - } catch (e) { - if (e instanceof UnwrapKeyError) { - notifyError( - i18n.str`Could not unlock account`, - e.message as TranslatedString, - ); - } else { - throw e; - } - } - }} - > -
- -
-
- -
-
+
+ + label={i18n.str`Password`} + name="password" + type="password" + required + handler={form.password} + /> +
+ +
+ +
+
- +
); diff --git a/packages/aml-backoffice-ui/src/pages/index.stories.ts b/packages/aml-backoffice-ui/src/pages/index.stories.ts index b2cbf485e..f11028de8 100644 --- a/packages/aml-backoffice-ui/src/pages/index.stories.ts +++ b/packages/aml-backoffice-ui/src/pages/index.stories.ts @@ -14,5 +14,4 @@ GNU Taler; see the file COPYING. If not, see */ export * as a1 from "./ShowConsolidated.stories.js"; -export * as a2 from "./AntiMoneyLaunderingForm.stories.js"; export * as a3 from "./Cases.stories.js"; diff --git a/packages/aml-backoffice-ui/src/utils/converter.ts b/packages/aml-backoffice-ui/src/utils/converter.ts index d2f05ed84..cca764a81 100644 --- a/packages/aml-backoffice-ui/src/utils/converter.ts +++ b/packages/aml-backoffice-ui/src/utils/converter.ts @@ -1,30 +1,46 @@ -import { AmlExchangeBackend } from "./types.js"; +/* + 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 { TalerExchangeApi } from "@gnu-taler/taler-util"; export const amlStateConverter = { toStringUI: stringifyAmlState, fromStringUI: parseAmlState, }; -function stringifyAmlState(s: AmlExchangeBackend.AmlState | undefined): string { +function stringifyAmlState(s: TalerExchangeApi.AmlState | undefined): string { if (s === undefined) return ""; switch (s) { - case AmlExchangeBackend.AmlState.normal: + case TalerExchangeApi.AmlState.normal: return "normal"; - case AmlExchangeBackend.AmlState.pending: + case TalerExchangeApi.AmlState.pending: return "pending"; - case AmlExchangeBackend.AmlState.frozen: + case TalerExchangeApi.AmlState.frozen: return "frozen"; } } -function parseAmlState(s: string | undefined): AmlExchangeBackend.AmlState { +function parseAmlState(s: string | undefined): TalerExchangeApi.AmlState { switch (s) { case "normal": - return AmlExchangeBackend.AmlState.normal; + return TalerExchangeApi.AmlState.normal; case "pending": - return AmlExchangeBackend.AmlState.pending; + return TalerExchangeApi.AmlState.pending; case "frozen": - return AmlExchangeBackend.AmlState.frozen; + return TalerExchangeApi.AmlState.frozen; default: throw Error(`unknown AML state: ${s}`); } diff --git a/packages/aml-backoffice-ui/src/utils/types.ts b/packages/aml-backoffice-ui/src/utils/types.ts deleted file mode 100644 index fd70d4e4d..000000000 --- a/packages/aml-backoffice-ui/src/utils/types.ts +++ /dev/null @@ -1,124 +0,0 @@ -export namespace AmlExchangeBackend { - // FIXME: placeholder - export interface AmlError { - code: number; - hint: string; - } - export interface AmlDecisionDetails { - // Array of AML decisions made for this account. Possibly - // contains only the most recent decision if "history" was - // not set to 'true'. - aml_history: AmlDecisionDetail[]; - - // Array of KYC attributes obtained for this account. - kyc_attributes: KycDetail[]; - } - - type AmlOfficerPublicKeyP = string; - - export interface AmlDecisionDetail { - // What was the justification given? - justification: string; - - // What is the new AML state. - new_state: Integer; - - // When was this decision made? - decision_time: Timestamp; - - // What is the new AML decision threshold (in monthly transaction volume)? - new_threshold: Amount; - - // Who made the decision? - decider_pub: AmlOfficerPublicKeyP; - } - export interface KycDetail { - // Name of the configuration section that specifies the provider - // which was used to collect the KYC details - provider_section: string; - - // The collected KYC data. NULL if the attribute data could not - // be decrypted (internal error of the exchange, likely the - // attribute key was changed). - attributes?: Object; - - // Time when the KYC data was collected - collection_time: Timestamp; - - // Time when the validity of the KYC data will expire - expiration_time: Timestamp; - } - - interface Timestamp { - // Seconds since epoch, or the special - // value "never" to represent an event that will - // never happen. - t_s: number | "never"; - } - - type PaytoHash = string; - type Integer = number; - type Amount = string; - // EdDSA signatures are transmitted as 64-bytes base32 - // binary-encoded objects with just the R and S values (base32_ binary-only). - type EddsaSignature = string; - - export interface AmlRecords { - // Array of AML records matching the query. - records: AmlRecord[]; - } - - interface AmlRecord { - // Which payto-address is this record about. - // Identifies a GNU Taler wallet or an affected bank account. - h_payto: PaytoHash; - - // What is the current AML state. - current_state: AmlState; - - // Monthly transaction threshold before a review will be triggered - threshold: Amount; - - // RowID of the record. - rowid: Integer; - } - - export enum AmlState { - normal = 0, - pending = 1, - frozen = 2, - } - - - export interface AmlDecision { - - // Human-readable justification for the decision. - justification: string; - - // At what monthly transaction volume should the - // decision be automatically reviewed? - new_threshold: Amount; - - // Which payto-address is the decision about? - // Identifies a GNU Taler wallet or an affected bank account. - h_payto: PaytoHash; - - // What is the new AML state (e.g. frozen, unfrozen, etc.) - // Numerical values are defined in AmlDecisionState. - new_state: Integer; - - // Signature by the AML officer over a - // TALER_MasterAmlOfficerStatusPS. - // Must have purpose TALER_SIGNATURE_MASTER_AML_KEY. - officer_sig: EddsaSignature; - - // When was the decision made? - decision_time: Timestamp; - - // Optional argument to impose new KYC requirements - // that the customer has to satisfy to unblock transactions. - kyc_requirements?: string[]; - } - - -} diff --git a/packages/web-util/src/forms/DefaultForm.tsx b/packages/web-util/src/forms/DefaultForm.tsx index 118699487..1c635e089 100644 --- a/packages/web-util/src/forms/DefaultForm.tsx +++ b/packages/web-util/src/forms/DefaultForm.tsx @@ -1,4 +1,4 @@ -import { Fragment, h } from "preact"; +import { Fragment, VNode, h } from "preact"; import { FormProvider, FormProviderProps, FormState } from "./FormProvider.js"; import { RenderAllFieldsByUiConfig, UIFormField } from "./forms.js"; import { TranslatedString } from "@gnu-taler/taler-util"; @@ -39,7 +39,7 @@ export function DefaultForm({ onSubmit, children, readOnly, -}: Omit, "computeFormState"> & { form: FlexibleForm }) { +}: Omit, "computeFormState"> & { form: FlexibleForm }): VNode { return (