import { Fragment, VNode, h } from "preact"; import { AmlDecisionDetail, AmlDecisionDetails, AmlState, KycDetail, } from "../types.js"; import { AbsoluteTime, AmountJson, Amounts, TranslatedString, } from "@gnu-taler/taler-util"; import { format } from "date-fns"; import { ArrowDownCircleIcon, ClockIcon } from "@heroicons/react/20/solid"; import { useState } from "preact/hooks"; import { NiceForm } from "../NiceForm.js"; import { FlexibleForm } from "../forms/index.js"; import { UIFormField } from "../handlers/forms.js"; import { Pages } from "../pages.js"; const response: AmlDecisionDetails = { aml_history: [ { justification: "Lack of documentation", decider_pub: "ASDASDASD", decision_time: { t_s: Date.now() / 1000, }, new_state: 2, new_threshold: "USD:0", }, { justification: "Doing a transfer of high amount", decider_pub: "ASDASDASD", decision_time: { t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 6, }, new_state: 1, new_threshold: "USD:2000", }, { justification: "Account is known to the system", decider_pub: "ASDASDASD", decision_time: { t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 9, }, new_state: 0, new_threshold: "USD:100", }, ], kyc_attributes: [ { collection_time: { t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 8, }, expiration_time: { t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 4, }, provider_section: "asdasd", attributes: { name: "Sebastian", }, }, { collection_time: { t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 5, }, expiration_time: { t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 2, }, provider_section: "asdasd", attributes: { creditCard: "12312312312", }, }, ], }; type AmlEvent = AmlFormEvent | KycCollectionEvent | KycExpirationEvent; type AmlFormEvent = { type: "aml-form"; when: AbsoluteTime; title: TranslatedString; state: AmlState; threshold: AmountJson; }; type KycCollectionEvent = { type: "kyc-collection"; when: AbsoluteTime; title: TranslatedString; values: object; provider: string; }; type KycExpirationEvent = { type: "kyc-expiration"; when: AbsoluteTime; title: TranslatedString; fields: string[]; }; type WithTime = { when: AbsoluteTime }; function selectSooner(a: WithTime, b: WithTime) { return AbsoluteTime.cmp(a.when, b.when); } function getEventsFromAmlHistory( aml: AmlDecisionDetail[], kyc: KycDetail[], ): AmlEvent[] { const ae: AmlEvent[] = aml.map((a) => { return { type: "aml-form", state: a.new_state, threshold: Amounts.parseOrThrow(a.new_threshold), title: a.justification as TranslatedString, when: { t_ms: a.decision_time.t_s === "never" ? "never" : a.decision_time.t_s * 1000, }, } as AmlEvent; }); const ke = kyc.reduce((prev, k) => { prev.push({ type: "kyc-collection", title: "collection" as TranslatedString, when: AbsoluteTime.fromProtocolTimestamp(k.collection_time), values: !k.attributes ? {} : k.attributes, provider: k.provider_section, }); prev.push({ type: "kyc-expiration", title: "expired" as TranslatedString, when: AbsoluteTime.fromProtocolTimestamp(k.expiration_time), fields: !k.attributes ? [] : Object.keys(k.attributes), }); return prev; }, [] as AmlEvent[]); return ae.concat(ke).sort(selectSooner); } export function CaseDetails({ account }: { account?: string }) { const events = getEventsFromAmlHistory( response.aml_history, response.kyc_attributes, ); console.log("DETAILS", events, events[events.length - 1 - 2]); const [selected, setSelected] = useState( events[events.length - 1 - 2], ); return (
New AML form

Case history

    {events.map((e, idx) => { const isLast = events.length - 1 === idx; return (
  • { setSelected(e); }} >
    {!isLast ? ( ) : undefined}
    {(() => { switch (e.type) { case "aml-form": { switch (e.state) { case AmlState.normal: { return (
    Normal {e.threshold.currency}{" "} {Amounts.stringifyValue(e.threshold)}
    ); } case AmlState.pending: { return (
    Pending {e.threshold.currency}{" "} {Amounts.stringifyValue(e.threshold)}
    ); } case AmlState.frozen: { return (
    Frozen {e.threshold.currency}{" "} {Amounts.stringifyValue(e.threshold)}
    ); } } } case "kyc-collection": { return ( ); } case "kyc-expiration": { return ; } } })()}

    {e.title}

    {e.when.t_ms === "never" ? ( "never" ) : ( )}
  • ); })}
{selected && } {selected && }
); } function ShowEventDetails({ event }: { event: AmlEvent }): VNode { return
type {event.type}
; } function ShowConsolidated({ history, until, }: { history: AmlEvent[]; until: AmlEvent; }): VNode { console.log("UNTIL", until); const cons = getConsolidated(history, until.when); const form: FlexibleForm = { versionId: "1", behavior: (form) => { return {}; }, design: [ { title: "AML" as TranslatedString, fields: [ { type: "amount", props: { label: "Threshold" as TranslatedString, name: "aml.threshold", }, }, { type: "choiceHorizontal", props: { label: "State" as TranslatedString, name: "aml.state", converter: amlStateConverter, choices: [ { label: "Frozen" as TranslatedString, value: AmlState.frozen, }, { label: "Pending" as TranslatedString, value: AmlState.pending, }, { label: "Normal" as TranslatedString, value: AmlState.normal, }, ], }, }, ], }, Object.entries(cons.kyc).length > 0 ? { title: "KYC" as TranslatedString, fields: Object.entries(cons.kyc).map(([key, field]) => { const result: UIFormField = { type: "text", props: { label: key as TranslatedString, name: `kyc.${key}.value`, help: `${field.provider} since ${ field.since.t_ms === "never" ? "never" : format(field.since.t_ms, "dd/MM/yyyy") }` as TranslatedString, }, }; return result; }), } : undefined, ], }; return (

Consolidated information after{" "} {until.when.t_ms === "never" ? "never" : format(until.when.t_ms, "dd MMMM yyyy")}

{}} />
); } interface Consolidated { aml: { state?: AmlState; threshold?: AmountJson; since: AbsoluteTime; }; kyc: { [field: string]: { value: any; provider: string; since: AbsoluteTime; }; }; } function getConsolidated( history: AmlEvent[], when: AbsoluteTime, ): Consolidated { const initial: Consolidated = { aml: { since: AbsoluteTime.never(), }, kyc: {}, }; return history.reduce((prev, cur) => { if (AbsoluteTime.cmp(when, cur.when) < 0) { return prev; } switch (cur.type) { case "kyc-expiration": { cur.fields.forEach((field) => { delete prev.kyc[field]; }); break; } case "aml-form": { prev.aml.threshold = cur.threshold; prev.aml.state = cur.state; prev.aml.since = cur.when; break; } case "kyc-collection": { Object.keys(cur.values).forEach((field) => { prev.kyc[field] = { value: (cur.values as any)[field], provider: cur.provider, since: cur.when, }; }); break; } } return prev; }, initial); } export const amlStateConverter = { toStringUI: stringifyAmlState, fromStringUI: parseAmlState, }; function stringifyAmlState(s: AmlState | undefined): string { if (s === undefined) return ""; switch (s) { case AmlState.normal: return "normal"; case AmlState.pending: return "pending"; case AmlState.frozen: return "frozen"; } } function parseAmlState(s: string | undefined): AmlState { switch (s) { case "normal": return AmlState.normal; case "pending": return AmlState.pending; case "frozen": return AmlState.frozen; default: throw Error(`unknown AML state: ${s}`); } }