commit 10836e529b9d5c4ee04f8ba71862d7e471fbcff2
parent 8ee287e65d5cfa28cc78f2bfedee6b6055140b1c
Author: Sebastian <sebasjm@gmail.com>
Date: Wed, 16 Apr 2025 19:33:12 -0300
fixes from QC
Diffstat:
11 files changed, 218 insertions(+), 129 deletions(-)
diff --git a/packages/aml-backoffice-ui/src/Routing.tsx b/packages/aml-backoffice-ui/src/Routing.tsx
@@ -314,7 +314,19 @@ function PrivateRouting(): VNode {
return <Accounts routeToCaseById={privatePages.caseDetails} />;
}
case "search": {
- return <Search />;
+ return (
+ <Search
+ onNewDecision={(request, account, payto) => {
+ updateRequest(request);
+ navigateTo(
+ privatePages.decideNew.url({
+ cid: account,
+ payto: payto,
+ }),
+ );
+ }}
+ />
+ );
}
case "statsDownload": {
return <div>not yet implemented</div>;
diff --git a/packages/aml-backoffice-ui/src/hooks/decision-request.ts b/packages/aml-backoffice-ui/src/hooks/decision-request.ts
@@ -80,7 +80,7 @@ export interface DecisionRequest {
/**
* If given all the information, this account need to be investigated
*/
- keep_investigating: boolean;
+ keep_investigating: boolean | undefined;
/**
* Description of the decision
*/
@@ -126,15 +126,9 @@ export const codecForDecisionRequest = (): Codec<DecisionRequest> =>
"triggering_events",
codecOptional(codecForList(codecForString())),
)
- .property(
- "keep_investigating",
- codecOptionalDefault(codecForBoolean(), false),
- )
+ .property("keep_investigating", codecOptional(codecForBoolean()))
.property("new_measures", codecOptional(codecForList(codecForString())))
- .property(
- "onExpire_measure",
- codecOptional(codecForString()),
- )
+ .property("onExpire_measure", codecOptional(codecForString()))
.build("DecisionRequest");
export const DECISION_REQUEST_EMPTY: DecisionRequest = {
@@ -146,7 +140,7 @@ export const DECISION_REQUEST_EMPTY: DecisionRequest = {
accountName: undefined,
triggering_events: undefined,
justification: undefined,
- keep_investigating: false,
+ keep_investigating: undefined,
new_measures: undefined,
properties: undefined,
rules: undefined,
diff --git a/packages/aml-backoffice-ui/src/pages/Dashboard.tsx b/packages/aml-backoffice-ui/src/pages/Dashboard.tsx
@@ -266,9 +266,9 @@ function labelForEvent_tops(
case TOPS_AmlEventsName.ACCOUNT_CLOSED_HIGH_RISK:
return i18n.str`High risk account removed`;
case TOPS_AmlEventsName.ACCOUNT_OPENED_DOMESTIC_PEP:
- return i18n.str`Account from dometic PEP incorporated`;
+ return i18n.str`Account from domestic PEP incorporated`;
case TOPS_AmlEventsName.ACCOUNT_CLOSED_DOMESTIC_PEP:
- return i18n.str`Account from dometic PEP removed`;
+ return i18n.str`Account from domestic PEP removed`;
case TOPS_AmlEventsName.ACCOUNT_OPENED_FOREIGN_PEP:
return i18n.str`Account from foreign PEP incorporated`;
case TOPS_AmlEventsName.ACCOUNT_CLOSED_FOREIGN_PEP:
@@ -278,9 +278,9 @@ function labelForEvent_tops(
case TOPS_AmlEventsName.ACCOUNT_CLOSED_HR_COUNTRY:
return i18n.str`Account from high-risk country removed`;
case TOPS_AmlEventsName.ACCOUNT_OPENED_INT_ORG_PEP:
- return i18n.str`Account from dometic PEP incorporated`;
+ return i18n.str`Account from domestic PEP incorporated`;
case TOPS_AmlEventsName.ACCOUNT_CLOSED_INT_ORG_PEP:
- return i18n.str`Account from dometic PEP removed`;
+ return i18n.str`Account from domestic PEP removed`;
case TOPS_AmlEventsName.ACCOUNT_MROS_REPORTED_SUSPICION_SIMPLE:
return i18n.str`MROS reported by obligation`;
case TOPS_AmlEventsName.ACCOUNT_MROS_REPORTED_SUSPICION_SUBSTANTIATED:
@@ -342,9 +342,9 @@ function descriptionForEvent_tops(
case TOPS_AmlEventsName.ACCOUNT_CLOSED_HIGH_RISK:
return i18n.str`High risk account removed`;
case TOPS_AmlEventsName.ACCOUNT_OPENED_DOMESTIC_PEP:
- return i18n.str`Account from dometic PEP incorporated`;
+ return i18n.str`Account from domestic PEP incorporated`;
case TOPS_AmlEventsName.ACCOUNT_CLOSED_DOMESTIC_PEP:
- return i18n.str`Account from dometic PEP removed`;
+ return i18n.str`Account from domestic PEP removed`;
case TOPS_AmlEventsName.ACCOUNT_OPENED_FOREIGN_PEP:
return i18n.str`Account from foreign PEP incorporated`;
case TOPS_AmlEventsName.ACCOUNT_CLOSED_FOREIGN_PEP:
diff --git a/packages/aml-backoffice-ui/src/pages/Search.tsx b/packages/aml-backoffice-ui/src/pages/Search.tsx
@@ -52,8 +52,13 @@ import { privatePages } from "../Routing.js";
import { Pagination, ToInvestigateIcon } from "./Cases.js";
import { HandleAccountNotReady } from "./HandleAccountNotReady.js";
import { Officer } from "./Officer.js";
+import { DECISION_REQUEST_EMPTY, DecisionRequest } from "../hooks/decision-request.js";
-export function Search() {
+export function Search({
+ onNewDecision,
+}: {
+ onNewDecision: (d: DecisionRequest, account: string, payto: string) => void;
+}) {
const officer = useOfficer();
const { i18n } = useTranslationContext();
@@ -110,12 +115,20 @@ export function Search() {
}
}
})()}
- {!paytoUri ? undefined : <ShowResult payto={paytoUri} />}
+ {!paytoUri ? undefined : (
+ <ShowResult payto={paytoUri} onNewDecision={onNewDecision} />
+ )}
</div>
);
}
-function ShowResult({ payto }: { payto: PaytoUri }): VNode {
+function ShowResult({
+ payto,
+ onNewDecision,
+}: {
+ payto: PaytoUri;
+ onNewDecision: (d: DecisionRequest, account: string, payto: string) => void;
+}): VNode {
const paytoStr = stringifyPaytoUri(payto);
const account = encodeCrock(hashNormalizedPaytoUri(paytoStr));
const { i18n } = useTranslationContext();
@@ -279,17 +292,20 @@ function ShowResult({ payto }: { payto: PaytoUri }): VNode {
There is no history known for this account yet.
</i18n.Translate>
- <a
- href={privatePages.decideNew.url({
- cid: account,
- payto: encodeCrockForURI(paytoStr),
- })}
+ <button
+ // href={privatePages.decideNew.url({
+ // cid: account,
+ // payto: encodeCrockForURI(paytoStr),
+ // })}
+ onClick={async () => {
+ onNewDecision(DECISION_REQUEST_EMPTY, account, encodeCrockForURI(paytoStr));
+ }}
class="text-indigo-600 hover:text-indigo-900"
>
<i18n.Translate>
You can make a decision for this account anyway.
</i18n.Translate>
- </a>
+ </button>
</Attention>
</div>
);
@@ -580,7 +596,7 @@ const walletFields: (i18n: InternationalizationAPI) => UIFormElementConfig[] = (
placeholder: i18n.str`abcdef1235`,
validator(value) {
return value && value.length !== 32
- ? i18n.str`Should be 16 characters`
+ ? i18n.str`Should be 32 characters`
: undefined;
},
},
diff --git a/packages/aml-backoffice-ui/src/pages/decision/AmlDecisionRequestWizard.tsx b/packages/aml-backoffice-ui/src/pages/decision/AmlDecisionRequestWizard.tsx
@@ -20,6 +20,7 @@ import {
TranslatedString
} from "@gnu-taler/taler-util";
import {
+ CopyButton,
useTranslationContext
} from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
@@ -71,7 +72,7 @@ const STEPS_ORDER_MAP = STEPS_ORDER.reduce(
);
export function isRulesCompleted(request: DecisionRequest): boolean {
- return request.rules !== undefined && request.deadline !== undefined && (AbsoluteTime.isNever(request.deadline) || !!request.onExpire_measure);
+ return request.rules !== undefined && request.deadline !== undefined;
}
export function isAttributesCompleted(request: DecisionRequest): boolean {
return request.attributes === undefined || request.attributes.errors === undefined;
@@ -110,7 +111,7 @@ export function AmlDecisionRequestWizard({
const content = (function () {
switch (stepOrDefault) {
case "rules":
- return <Rules account={account} />;
+ return <Rules account={account} newPayto={newPayto} />;
case "properties":
return <Properties account={account} />;
case "events":
@@ -118,7 +119,7 @@ export function AmlDecisionRequestWizard({
case "measures":
return <Measures />;
case "justification":
- return <Justification newPayto={newPayto} />;
+ return <Justification account={account} newPayto={newPayto} />;
case "attributes":
return <Attributes formId={formId}/>;
case "summary":
@@ -128,7 +129,16 @@ export function AmlDecisionRequestWizard({
})();
return (
- <div>
+ <div class="min-w-60">
+
+ <header class="flex items-center justify-between border-b border-white/5 px-4 py-4 sm:px-6 sm:py-6 lg:px-8">
+ <h1 class="text-base font-semibold leading-7 text-black">
+ <i18n.Translate>Decision for account: </i18n.Translate>
+ </h1>
+ <div>{account}</div>
+ <CopyButton class="" getContent={() => account} />
+ </header>
+
<WizardSteps step={stepOrDefault} onMove={onMove} newAccount={!!newPayto} />
<button
disabled={!STEPS_ORDER_MAP[stepOrDefault].prev}
diff --git a/packages/aml-backoffice-ui/src/pages/decision/Justification.tsx b/packages/aml-backoffice-ui/src/pages/decision/Justification.tsx
@@ -1,42 +1,53 @@
-import {
- AbsoluteTime,
- Duration,
- MeasureInformation,
- PaytoString,
- TalerError,
-} from "@gnu-taler/taler-util";
+import { AmlDecision, PaytoString, TalerError } from "@gnu-taler/taler-util";
import {
FormDesign,
FormUI,
InternationalizationAPI,
+ Loading,
onComponentUnload,
- UIHandlerId,
useForm,
useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { h, VNode } from "preact";
import { useCurrentDecisionRequest } from "../../hooks/decision-request.js";
-import { useServerMeasures } from "../../hooks/server-info.js";
+import { useAccountActiveDecision } from "../../hooks/decisions.js";
/**
* Mark for further investigation and explain decision
* @param param0
* @returns
*/
-export function Justification({ newPayto }: { newPayto?: PaytoString }): VNode {
+export function Justification({
+ account,
+ newPayto,
+}: {
+ account: string;
+ newPayto?: PaytoString;
+}): VNode {
+ const isNewAccount = !!newPayto;
+
+ const activeDecision = useAccountActiveDecision(isNewAccount ? undefined :account);
+ const info =
+ !activeDecision ||
+ activeDecision instanceof TalerError ||
+ activeDecision.type === "fail"
+ ? undefined
+ : activeDecision.body;
+
+ if (!info && !isNewAccount) {
+ return <Loading />;
+ }
+
+ return <JustificationForm isNewAccount={isNewAccount} info={info}/>
+}
+
+function JustificationForm({info, isNewAccount}:{info: AmlDecision | undefined, isNewAccount: boolean}):VNode {
const { i18n } = useTranslationContext();
const [request, _, updateRequest] = useCurrentDecisionRequest();
- const measures = useServerMeasures();
- const measureList =
- !measures || measures instanceof TalerError || measures.type === "fail"
- ? []
- : Object.entries(measures.body.roots).map(([id, mi]) => ({ id, ...mi }));
-
- const unknownAccount = !!newPayto
- const design = formDesign(i18n, measureList, unknownAccount);
+ const design = formDesign(i18n, isNewAccount);
const form = useForm<FormType>(design, {
- investigate: request.keep_investigating,
+ investigate: request.keep_investigating ?? info?.to_investigate ?? false,
justification: request.justification,
accountName: request.accountName,
});
@@ -46,7 +57,7 @@ export function Justification({ newPayto }: { newPayto?: PaytoString }): VNode {
...request,
keep_investigating: !!form.status.result.investigate,
justification: form.status.result.justification ?? "",
- accountName: form.status.result.justification ?? "",
+ accountName: form.status.result.accountName ?? "",
});
});
@@ -66,8 +77,7 @@ type FormType = {
const formDesign = (
i18n: InternationalizationAPI,
- mi: (MeasureInformation & { id: string })[],
- unknownAccount: boolean
+ unknownAccount: boolean,
): FormDesign<FormType> => ({
type: "single-column",
fields: [
@@ -87,7 +97,7 @@ const formDesign = (
type: "text",
label: i18n.str`Account holder`,
required: true,
- help:i18n.str`Full name of the account holder`,
+ help: i18n.str`Full name of the account holder`,
hidden: !unknownAccount,
},
],
diff --git a/packages/aml-backoffice-ui/src/pages/decision/Properties.tsx b/packages/aml-backoffice-ui/src/pages/decision/Properties.tsx
@@ -32,6 +32,7 @@ import {
import { useAccountActiveDecision } from "../../hooks/decisions.js";
import { usePreferences } from "../../hooks/preferences.js";
import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js";
+import { DEFAULT_LIMITS_WHEN_NEW_ACCOUNT } from "./Rules.js";
/**
* Update account properties
@@ -68,16 +69,14 @@ export function Properties({ account }: { account: string }): VNode {
(pref.testingDialect ? undefined : config.config.aml_spa_dialect) ??
AmlSpaDialect.TESTING;
- const calculatedProps = !lastDecision
- ? undefined
- : calculatePropertiesBasedOnState(
- lastDecision.limits,
- lastDecision.properties ?? {},
+ const calculatedProps = calculatePropertiesBasedOnState(
+ lastDecision?.limits ?? DEFAULT_LIMITS_WHEN_NEW_ACCOUNT,
+ lastDecision?.properties ?? {},
request,
dialect,
);
- const merged = !calculatedProps ? {} : Object.entries(calculatedProps).reduce(
+ const merged = Object.entries(calculatedProps).reduce(
(prev, [key, value]) => {
if (prev[key] === undefined) {
prev[key] = !!value;
diff --git a/packages/aml-backoffice-ui/src/pages/decision/Rules.tsx b/packages/aml-backoffice-ui/src/pages/decision/Rules.tsx
@@ -9,6 +9,7 @@ import {
LegitimizationRuleSet,
LimitOperationType,
MeasureInformation,
+ PaytoString,
TalerError,
TranslatedString,
} from "@gnu-taler/taler-util";
@@ -31,17 +32,30 @@ import { ShowDecisionLimitInfo } from "../CaseDetails.js";
import { RulesInfo } from "../RulesInfo.js";
const DEFAULT_MEASURE_IF_NONE = ["VERBOTEN"];
+export const DEFAULT_LIMITS_WHEN_NEW_ACCOUNT: LegitimizationRuleSet = {
+ custom_measures: {},
+ expiration_time: AbsoluteTime.toProtocolTimestamp(AbsoluteTime.never()),
+ rules:[],
+}
+
/**
* Defined new limits for the account
* @param param0
* @returns
*/
-export function Rules({ account }: { account: string }): VNode {
- const activeDecision = useAccountActiveDecision(account);
-
+export function Rules({
+ account,
+ newPayto,
+}: {
+ account: string;
+ newPayto?: PaytoString;
+}): VNode {
const { i18n } = useTranslationContext();
const { config } = useExchangeApiContext();
+
+ const isNewAccount = !!newPayto;
+
// const [request, updateRequestField, updateRequest] =
// useCurrentDecisionRequest();
const measures = useServerMeasures();
@@ -51,6 +65,9 @@ export function Rules({ account }: { account: string }): VNode {
? undefined
: measures.body.roots;
+ const activeDecision = useAccountActiveDecision(
+ isNewAccount ? undefined : account,
+ );
const info =
!activeDecision ||
activeDecision instanceof TalerError ||
@@ -58,7 +75,7 @@ export function Rules({ account }: { account: string }): VNode {
? undefined
: activeDecision.body;
- if (!info) {
+ if (!info && !isNewAccount) {
return <Loading />;
}
@@ -67,23 +84,31 @@ export function Rules({ account }: { account: string }): VNode {
<UpdateRulesForm
rootMeasures={rootMeasures}
config={config.config}
- limits={info.limits}
+ limits={info?.limits ?? DEFAULT_LIMITS_WHEN_NEW_ACCOUNT}
/>
<div>
<h2 class="mt-4 mb-2">
<i18n.Translate>Current active rules</i18n.Translate>
</h2>
- <ShowDecisionLimitInfo
- fixed
- since={AbsoluteTime.fromProtocolTimestamp(info.decision_time)}
- until={AbsoluteTime.fromProtocolTimestamp(
- info.limits.expiration_time,
- )}
- rules={info.limits.rules}
- startOpen
- measure={info.limits.successor_measure ?? ""}
- />
+ {info === undefined ? (
+ <p>
+ <i18n.Translate>
+ There are no rules for this account.
+ </i18n.Translate>
+ </p>
+ ) : (
+ <ShowDecisionLimitInfo
+ fixed
+ since={AbsoluteTime.fromProtocolTimestamp(info.decision_time)}
+ until={AbsoluteTime.fromProtocolTimestamp(
+ info.limits.expiration_time,
+ )}
+ rules={info.limits.rules}
+ startOpen
+ measure={info.limits.successor_measure ?? ""}
+ />
+ )}
</div>
</div>
);
@@ -184,10 +209,7 @@ function UpdateRulesForm({
</button>
<button
onClick={() => {
- updateRequestField(
- "rules",
- FREEZE_PLAN(config.currency),
- );
+ updateRequestField("rules", FREEZE_PLAN(config.currency));
}}
class="m-4 rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
>
@@ -195,10 +217,7 @@ function UpdateRulesForm({
</button>
<button
onClick={() => {
- updateRequestField(
- "rules",
- BASIC_PLAN(config.currency),
- );
+ updateRequestField("rules", BASIC_PLAN(config.currency));
}}
class="m-4 rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
>
@@ -206,10 +225,7 @@ function UpdateRulesForm({
</button>
<button
onClick={() => {
- updateRequestField(
- "rules",
- PREMIUM_PLAN(config.currency),
- );
+ updateRequestField("rules", PREMIUM_PLAN(config.currency));
}}
class="m-4 rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
>
@@ -503,7 +519,9 @@ const BASIC_PLAN: (currency: string) => KycRule[] = (currency) => [
fraction: 0,
value: 1000,
}),
- timeframe: Duration.toTalerProtocolDuration(Duration.fromSpec({months:1})),
+ timeframe: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ months: 1 }),
+ ),
},
{
display_priority: 1,
@@ -514,7 +532,9 @@ const BASIC_PLAN: (currency: string) => KycRule[] = (currency) => [
fraction: 0,
value: 1000,
}),
- timeframe: Duration.toTalerProtocolDuration(Duration.fromSpec({months:1})),
+ timeframe: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ months: 1 }),
+ ),
},
{
display_priority: 1,
@@ -523,9 +543,11 @@ const BASIC_PLAN: (currency: string) => KycRule[] = (currency) => [
threshold: Amounts.stringify({
currency,
fraction: 0,
- value: 5*1000,
+ value: 5 * 1000,
}),
- timeframe: Duration.toTalerProtocolDuration(Duration.fromSpec({months:1})),
+ timeframe: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ months: 1 }),
+ ),
},
{
display_priority: 1,
@@ -534,9 +556,11 @@ const BASIC_PLAN: (currency: string) => KycRule[] = (currency) => [
threshold: Amounts.stringify({
currency,
fraction: 0,
- value: 50*1000,
+ value: 50 * 1000,
}),
- timeframe: Duration.toTalerProtocolDuration(Duration.fromSpec({years:1})),
+ timeframe: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ years: 1 }),
+ ),
},
{
display_priority: 1,
@@ -545,9 +569,11 @@ const BASIC_PLAN: (currency: string) => KycRule[] = (currency) => [
threshold: Amounts.stringify({
currency,
fraction: 0,
- value: 5*1000,
+ value: 5 * 1000,
}),
- timeframe: Duration.toTalerProtocolDuration(Duration.fromSpec({months:1})),
+ timeframe: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ months: 1 }),
+ ),
},
{
display_priority: 1,
@@ -556,11 +582,12 @@ const BASIC_PLAN: (currency: string) => KycRule[] = (currency) => [
threshold: Amounts.stringify({
currency,
fraction: 0,
- value: 50*1000,
+ value: 50 * 1000,
}),
- timeframe: Duration.toTalerProtocolDuration(Duration.fromSpec({years:1})),
+ timeframe: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ years: 1 }),
+ ),
},
-
];
const PREMIUM_PLAN: (currency: string) => KycRule[] = (currency) => [
@@ -571,7 +598,7 @@ const PREMIUM_PLAN: (currency: string) => KycRule[] = (currency) => [
threshold: Amounts.stringify({
currency,
fraction: 0,
- value: 10*1000,
+ value: 10 * 1000,
}),
timeframe: Duration.toTalerProtocolDuration(Duration.getForever()),
},
@@ -595,7 +622,9 @@ const PREMIUM_PLAN: (currency: string) => KycRule[] = (currency) => [
fraction: 0,
value: 1000,
}),
- timeframe: Duration.toTalerProtocolDuration(Duration.fromSpec({months:1})),
+ timeframe: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ months: 1 }),
+ ),
},
{
display_priority: 1,
@@ -606,7 +635,9 @@ const PREMIUM_PLAN: (currency: string) => KycRule[] = (currency) => [
fraction: 0,
value: 1000,
}),
- timeframe: Duration.toTalerProtocolDuration(Duration.fromSpec({months:1})),
+ timeframe: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ months: 1 }),
+ ),
},
{
display_priority: 1,
@@ -615,9 +646,11 @@ const PREMIUM_PLAN: (currency: string) => KycRule[] = (currency) => [
threshold: Amounts.stringify({
currency,
fraction: 0,
- value: 15*1000,
+ value: 15 * 1000,
}),
- timeframe: Duration.toTalerProtocolDuration(Duration.fromSpec({months:1})),
+ timeframe: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ months: 1 }),
+ ),
},
{
display_priority: 1,
@@ -626,9 +659,11 @@ const PREMIUM_PLAN: (currency: string) => KycRule[] = (currency) => [
threshold: Amounts.stringify({
currency,
fraction: 0,
- value: 150*1000,
+ value: 150 * 1000,
}),
- timeframe: Duration.toTalerProtocolDuration(Duration.fromSpec({years:1})),
+ timeframe: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ years: 1 }),
+ ),
},
{
display_priority: 1,
@@ -637,9 +672,11 @@ const PREMIUM_PLAN: (currency: string) => KycRule[] = (currency) => [
threshold: Amounts.stringify({
currency,
fraction: 0,
- value: 15*1000,
+ value: 15 * 1000,
}),
- timeframe: Duration.toTalerProtocolDuration(Duration.fromSpec({months:1})),
+ timeframe: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ months: 1 }),
+ ),
},
{
display_priority: 1,
@@ -648,20 +685,19 @@ const PREMIUM_PLAN: (currency: string) => KycRule[] = (currency) => [
threshold: Amounts.stringify({
currency,
fraction: 0,
- value: 150*1000,
+ value: 150 * 1000,
}),
- timeframe: Duration.toTalerProtocolDuration(Duration.fromSpec({years:1})),
+ timeframe: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ years: 1 }),
+ ),
},
];
-const FREEZE_PLAN: (currency: string) => KycRule[] = (currency) => Object.values(LimitOperationType).map((operation_type) => ({
- display_priority: 1,
- measures: ["VERBOTEN"],
- operation_type,
- threshold: Amounts.stringify(
- Amounts.zeroOfCurrency(currency),
- ),
- timeframe: Duration.toTalerProtocolDuration(
- Duration.getForever(),
- ),
-}))
-\ No newline at end of file
+const FREEZE_PLAN: (currency: string) => KycRule[] = (currency) =>
+ Object.values(LimitOperationType).map((operation_type) => ({
+ display_priority: 1,
+ measures: ["VERBOTEN"],
+ operation_type,
+ threshold: Amounts.stringify(Amounts.zeroOfCurrency(currency)),
+ timeframe: Duration.toTalerProtocolDuration(Duration.getForever()),
+ }));
diff --git a/packages/aml-backoffice-ui/src/pages/decision/Summary.tsx b/packages/aml-backoffice-ui/src/pages/decision/Summary.tsx
@@ -119,7 +119,7 @@ export function Summary({
),
justification: decision.justification!,
payto_uri: !fullPayto ? undefined : stringifyPaytoUri(fullPayto),
- keep_investigating: decision.keep_investigating,
+ keep_investigating: decision.keep_investigating ?? false,
new_rules: {
expiration_time: AbsoluteTime.toProtocolTimestamp(
decision.deadline!,
diff --git a/packages/web-util/src/forms/forms-ui.tsx b/packages/web-util/src/forms/forms-ui.tsx
@@ -313,7 +313,7 @@ export function ErrorsSummary<T>({
>
<div class="px-4 sm:px-0">
<h3 class="text-base/7 font-semibold text-gray-900">
- <i18n.Translate>Errors summary</i18n.Translate>
+ <i18n.Translate>Missing fields</i18n.Translate>
</h3>
</div>
@@ -397,9 +397,15 @@ export function ErrorsSummary<T>({
}}
class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0 odd:bg-white even:bg-gray-100 cursor-pointer"
>
- <dt class="underline pl-4 text-sm/6 font-medium text-gray-900">
- {errHandler.label}
- </dt>
+ {errHandler.section ? (
+ <dt class="underline pl-4 text-sm/6 font-medium text-gray-900">
+ {errHandler.section}: {errHandler.label}
+ </dt>
+ ) : (
+ <dt class="underline pl-4 text-sm/6 font-medium text-gray-900">
+ {errHandler.label}
+ </dt>
+ )}
<dd class="underline flex text-sm/6 text-red-700 sm:col-span-2 sm:mt-0">
{errHandler.message}
</dd>
diff --git a/packages/web-util/src/hooks/useForm.ts b/packages/web-util/src/hooks/useForm.ts
@@ -100,6 +100,7 @@ export type RecursivePartial<T> = {
export type ErrorAndLabel = {
message: TranslatedString;
label: TranslatedString;
+ section: TranslatedString | undefined;
};
export type FormErrors<T> = {
@@ -223,6 +224,7 @@ function checkFormFieldIsValid(
formElement: UIFormElementConfig,
currentValue: string | undefined,
i18n: InternationalizationAPI,
+ secitonTitle: string | undefined,
): ErrorAndLabel | undefined {
if (!("id" in formElement)) {
return undefined;
@@ -232,6 +234,7 @@ function checkFormFieldIsValid(
return {
label: formElement.label as TranslatedString,
message: i18n.str`required`,
+ section: secitonTitle as TranslatedString,
};
} else if (formElement.validator) {
try {
@@ -240,6 +243,7 @@ function checkFormFieldIsValid(
return {
label: formElement.label as TranslatedString,
message,
+ section: secitonTitle as TranslatedString,
};
}
} catch (e) {
@@ -251,6 +255,7 @@ function checkFormFieldIsValid(
return {
label: formElement.label as TranslatedString,
message,
+ section: secitonTitle as TranslatedString,
};
}
}
@@ -278,6 +283,7 @@ function constructFormHandler<T>(
formElement: UIFormElementConfig,
hiddenSection: boolean | undefined,
handlerUiPath: string,
+ secitonTitle: string | undefined,
): void {
if (!("id" in formElement)) {
return undefined;
@@ -296,7 +302,7 @@ function constructFormHandler<T>(
(formElement.hide && formElement.hide(currentValue, result));
const currentError: ErrorAndLabel | undefined = !hidden
- ? checkFormFieldIsValid(formElement, currentValue, i18n)
+ ? checkFormFieldIsValid(formElement, currentValue, i18n, secitonTitle)
: undefined;
if (currentError !== undefined) {
@@ -329,15 +335,16 @@ function constructFormHandler<T>(
if (hidden) {
model.hiddenSections.add(`${secIndex}`);
}
+
sec.fields.forEach((f, fieldIndex) =>
- createFieldHandler(f, hidden, `${secIndex}.${fieldIndex}`),
+ createFieldHandler(f, hidden, `${secIndex}.${fieldIndex}`, sec.title),
);
});
break;
}
case "single-column": {
design.fields.forEach((f, fieldIndex) =>
- createFieldHandler(f, undefined, `root.${fieldIndex}`),
+ createFieldHandler(f, undefined, `root.${fieldIndex}`, undefined),
);
break;
}