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 --- .../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 - 12 files changed, 430 insertions(+), 570 deletions(-) delete mode 100644 packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.stories.tsx delete mode 100644 packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx (limited to 'packages/aml-backoffice-ui/src/pages') 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"; -- cgit v1.2.3