make signed request to exchange
This commit is contained in:
parent
24d05d87ec
commit
2335c3418c
@ -1,14 +1,20 @@
|
||||
import {
|
||||
bytesToString,
|
||||
PaytoUri,
|
||||
TalerSignaturePurpose,
|
||||
bufferForUint32,
|
||||
buildSigPS,
|
||||
createEddsaKeyPair,
|
||||
decodeCrock,
|
||||
decryptWithDerivedKey,
|
||||
eddsaGetPublic,
|
||||
eddsaSign,
|
||||
encodeCrock,
|
||||
encryptWithDerivedKey,
|
||||
getRandomBytesF,
|
||||
stringToBytes,
|
||||
stringifyPaytoUri,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { AmlExchangeBackend } from "./types.js";
|
||||
|
||||
export interface Account {
|
||||
accountId: AccountId;
|
||||
@ -45,6 +51,26 @@ export async function unlockAccount(
|
||||
return { accountId, signingKey };
|
||||
}
|
||||
|
||||
export function buildQuerySignature(key: SigningKey): string {
|
||||
const sigBlob = buildSigPS(
|
||||
TalerSignaturePurpose.TALER_SIGNATURE_AML_QUERY,
|
||||
).build();
|
||||
|
||||
return encodeCrock(eddsaSign(sigBlob, key));
|
||||
}
|
||||
export function buildDecisionSignature(
|
||||
key: SigningKey,
|
||||
payto: PaytoUri,
|
||||
state: AmlExchangeBackend.AmlState,
|
||||
): string {
|
||||
const sigBlob = buildSigPS(TalerSignaturePurpose.TALER_SIGNATURE_AML_DECISION)
|
||||
.put(decodeCrock(stringifyPaytoUri(payto)))
|
||||
.put(bufferForUint32(state))
|
||||
.build();
|
||||
|
||||
return encodeCrock(eddsaSign(sigBlob, key));
|
||||
}
|
||||
|
||||
declare const opaque_Account: unique symbol;
|
||||
export type LockedAccount = string & { [opaque_Account]: true };
|
||||
|
||||
|
@ -1,12 +1,7 @@
|
||||
import {
|
||||
AbsoluteTime,
|
||||
AmountJson,
|
||||
TranslatedString,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { TranslatedString } from "@gnu-taler/taler-util";
|
||||
import { FormState } from "../handlers/FormProvider.js";
|
||||
import { FlexibleForm } from "./index.js";
|
||||
import { State } from "../pages/AntiMoneyLaunderingForm.js";
|
||||
import { AmlState } from "../types.js";
|
||||
import { FlexibleForm } from "./index.js";
|
||||
import { Simplest, resolutionSection } from "./simplest.js";
|
||||
|
||||
export const v1 = (current: State): FlexibleForm<Form902_11.Form> => ({
|
||||
|
@ -1,12 +1,7 @@
|
||||
import {
|
||||
AbsoluteTime,
|
||||
AmountJson,
|
||||
TranslatedString,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { AbsoluteTime, TranslatedString } from "@gnu-taler/taler-util";
|
||||
import { FormState } from "../handlers/FormProvider.js";
|
||||
import { FlexibleForm } from "./index.js";
|
||||
import { State } from "../pages/AntiMoneyLaunderingForm.js";
|
||||
import { AmlState } from "../types.js";
|
||||
import { FlexibleForm } from "./index.js";
|
||||
import { Simplest, resolutionSection } from "./simplest.js";
|
||||
|
||||
export const v1 = (current: State): FlexibleForm<Form902_12.Form> => ({
|
||||
|
@ -1,12 +1,7 @@
|
||||
import {
|
||||
AbsoluteTime,
|
||||
AmountJson,
|
||||
TranslatedString,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { AbsoluteTime, TranslatedString } from "@gnu-taler/taler-util";
|
||||
import { FormState } from "../handlers/FormProvider.js";
|
||||
import { FlexibleForm } from "./index.js";
|
||||
import { State } from "../pages/AntiMoneyLaunderingForm.js";
|
||||
import { AmlState } from "../types.js";
|
||||
import { FlexibleForm } from "./index.js";
|
||||
import { Simplest, resolutionSection } from "./simplest.js";
|
||||
|
||||
export const v1 = (current: State): FlexibleForm<Form902_13.Form> => ({
|
||||
|
@ -1,12 +1,7 @@
|
||||
import {
|
||||
AbsoluteTime,
|
||||
AmountJson,
|
||||
TranslatedString,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { AbsoluteTime, TranslatedString } from "@gnu-taler/taler-util";
|
||||
import { FormState } from "../handlers/FormProvider.js";
|
||||
import { FlexibleForm } from "./index.js";
|
||||
import { State } from "../pages/AntiMoneyLaunderingForm.js";
|
||||
import { AmlState } from "../types.js";
|
||||
import { FlexibleForm } from "./index.js";
|
||||
import { Simplest, resolutionSection } from "./simplest.js";
|
||||
|
||||
export const v1 = (current: State): FlexibleForm<Form902_15.Form> => ({
|
||||
|
@ -1,14 +1,7 @@
|
||||
import {
|
||||
AbsoluteTime,
|
||||
AmountJson,
|
||||
Amounts,
|
||||
TranslatedString,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { FlexibleForm, languageList } from "./index.js";
|
||||
import { AbsoluteTime, TranslatedString } from "@gnu-taler/taler-util";
|
||||
import { FormState } from "../handlers/FormProvider.js";
|
||||
import { State } from "../pages/AntiMoneyLaunderingForm.js";
|
||||
import { AmlState } from "../types.js";
|
||||
import { amlStateConverter } from "../pages/CaseDetails.js";
|
||||
import { FlexibleForm, languageList } from "./index.js";
|
||||
import { Simplest, resolutionSection } from "./simplest.js";
|
||||
|
||||
export const v1 = (current: State): FlexibleForm<Form902_1.Form> => ({
|
||||
|
@ -1,17 +1,10 @@
|
||||
import {
|
||||
AbsoluteTime,
|
||||
AmountJson,
|
||||
Amounts,
|
||||
TranslatedString,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { FormState } from "../handlers/FormProvider.js";
|
||||
import { FlexibleForm } from "./index.js";
|
||||
import { AbsoluteTime, TranslatedString } from "@gnu-taler/taler-util";
|
||||
import { ArrowRightIcon } from "@heroicons/react/24/outline";
|
||||
import { h as create } from "preact";
|
||||
import { ChevronRightIcon } from "@heroicons/react/24/solid";
|
||||
import { h as create } from "preact";
|
||||
import { FormState } from "../handlers/FormProvider.js";
|
||||
import { State } from "../pages/AntiMoneyLaunderingForm.js";
|
||||
import { AmlState } from "../types.js";
|
||||
import { amlStateConverter } from "../pages/CaseDetails.js";
|
||||
import { FlexibleForm } from "./index.js";
|
||||
import { Simplest, resolutionSection } from "./simplest.js";
|
||||
|
||||
export const v1 = (current: State): FlexibleForm<Form902_4.Form> => ({
|
||||
|
@ -1,12 +1,7 @@
|
||||
import {
|
||||
AbsoluteTime,
|
||||
AmountJson,
|
||||
TranslatedString,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { TranslatedString } from "@gnu-taler/taler-util";
|
||||
import { FormState } from "../handlers/FormProvider.js";
|
||||
import { FlexibleForm, currencyList } from "./index.js";
|
||||
import { State } from "../pages/AntiMoneyLaunderingForm.js";
|
||||
import { AmlState } from "../types.js";
|
||||
import { FlexibleForm, currencyList } from "./index.js";
|
||||
import { Simplest, resolutionSection } from "./simplest.js";
|
||||
|
||||
export const v1 = (current: State): FlexibleForm<Form902_5.Form> => ({
|
||||
|
@ -1,12 +1,7 @@
|
||||
import {
|
||||
AbsoluteTime,
|
||||
AmountJson,
|
||||
TranslatedString,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { AbsoluteTime, TranslatedString } from "@gnu-taler/taler-util";
|
||||
import { FormState } from "../handlers/FormProvider.js";
|
||||
import { FlexibleForm } from "./index.js";
|
||||
import { State } from "../pages/AntiMoneyLaunderingForm.js";
|
||||
import { AmlState } from "../types.js";
|
||||
import { FlexibleForm } from "./index.js";
|
||||
import { Simplest, resolutionSection } from "./simplest.js";
|
||||
|
||||
export const v1 = (current: State): FlexibleForm<Form902_9.Form> => ({
|
||||
|
@ -5,11 +5,11 @@ import {
|
||||
TranslatedString,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { FormState } from "../handlers/FormProvider.js";
|
||||
import { FlexibleForm } from "./index.js";
|
||||
import { AmlState } from "../types.js";
|
||||
import { amlStateConverter } from "../pages/CaseDetails.js";
|
||||
import { DoubleColumnFormSection } from "../handlers/forms.js";
|
||||
import { State } from "../pages/AntiMoneyLaunderingForm.js";
|
||||
import { DoubleColumnFormSection, UIFormField } from "../handlers/forms.js";
|
||||
import { amlStateConverter } from "../pages/CaseDetails.js";
|
||||
import { AmlExchangeBackend } from "../types.js";
|
||||
import { FlexibleForm } from "./index.js";
|
||||
|
||||
export const v1 = (current: State): FlexibleForm<Simplest.Form> => ({
|
||||
versionId: "2023-05-25",
|
||||
@ -36,7 +36,7 @@ export const v1 = (current: State): FlexibleForm<Simplest.Form> => ({
|
||||
disabled: true,
|
||||
},
|
||||
threshold: {
|
||||
disabled: v.state === AmlState.frozen,
|
||||
disabled: v.state === AmlExchangeBackend.AmlState.frozen,
|
||||
},
|
||||
};
|
||||
},
|
||||
@ -46,7 +46,7 @@ export namespace Simplest {
|
||||
export interface WithResolution {
|
||||
when: AbsoluteTime;
|
||||
threshold: AmountJson;
|
||||
state: AmlState;
|
||||
state: AmlExchangeBackend.AmlState;
|
||||
}
|
||||
export interface Form extends WithResolution {
|
||||
comment: string;
|
||||
@ -77,15 +77,15 @@ export function resolutionSection(current: State): DoubleColumnFormSection {
|
||||
converter: amlStateConverter,
|
||||
choices: [
|
||||
{
|
||||
value: AmlState.frozen,
|
||||
value: AmlExchangeBackend.AmlState.frozen,
|
||||
label: "Frozen" as TranslatedString,
|
||||
},
|
||||
{
|
||||
value: AmlState.pending,
|
||||
value: AmlExchangeBackend.AmlState.pending,
|
||||
label: "Pending" as TranslatedString,
|
||||
},
|
||||
{
|
||||
value: AmlState.normal,
|
||||
value: AmlExchangeBackend.AmlState.normal,
|
||||
label: "Normal" as TranslatedString,
|
||||
},
|
||||
],
|
||||
|
81
packages/aml-backoffice-ui/src/hooks/useBackend.ts
Normal file
81
packages/aml-backoffice-ui/src/hooks/useBackend.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import {
|
||||
HttpResponseOk,
|
||||
RequestOptions,
|
||||
useApiContext,
|
||||
} from "@gnu-taler/web-util/browser";
|
||||
import { useCallback } from "preact/hooks";
|
||||
import { uiSettings } from "../settings.js";
|
||||
import { canonicalizeBaseUrl } from "@gnu-taler/taler-util";
|
||||
import { useOfficer } from "./useOfficer.js";
|
||||
import { buildQuerySignature } from "../account.js";
|
||||
|
||||
interface useBackendType {
|
||||
request: <T>(
|
||||
path: string,
|
||||
options?: RequestOptions,
|
||||
) => Promise<HttpResponseOk<T>>;
|
||||
fetcher: <T>(endpoint: string) => Promise<HttpResponseOk<T>>;
|
||||
paginatedFetcher: <T>(
|
||||
args: [string, number, number, string],
|
||||
) => Promise<HttpResponseOk<T>>;
|
||||
}
|
||||
export function usePublicBackend(): useBackendType {
|
||||
const { request: requestHandler } = useApiContext();
|
||||
|
||||
const baseUrl = getInitialBackendBaseURL();
|
||||
|
||||
const request = useCallback(
|
||||
function requestImpl<T>(
|
||||
path: string,
|
||||
options: RequestOptions = {},
|
||||
): Promise<HttpResponseOk<T>> {
|
||||
return requestHandler<T>(baseUrl, path, options);
|
||||
},
|
||||
[baseUrl],
|
||||
);
|
||||
|
||||
const fetcher = useCallback(
|
||||
function fetcherImpl<T>(endpoint: string): Promise<HttpResponseOk<T>> {
|
||||
return requestHandler<T>(baseUrl, endpoint);
|
||||
},
|
||||
[baseUrl],
|
||||
);
|
||||
const paginatedFetcher = useCallback(
|
||||
function fetcherImpl<T>([endpoint, page, size, talerAmlOfficerSignature]: [
|
||||
string,
|
||||
number,
|
||||
number,
|
||||
string,
|
||||
]): Promise<HttpResponseOk<T>> {
|
||||
return requestHandler<T>(baseUrl, endpoint, {
|
||||
params: { page: page || 1, size },
|
||||
talerAmlOfficerSignature,
|
||||
});
|
||||
},
|
||||
[baseUrl],
|
||||
);
|
||||
return {
|
||||
request,
|
||||
fetcher,
|
||||
paginatedFetcher,
|
||||
};
|
||||
}
|
||||
|
||||
export function getInitialBackendBaseURL(): string {
|
||||
const overrideUrl =
|
||||
typeof localStorage !== "undefined"
|
||||
? localStorage.getItem("exchange-aml-base-url")
|
||||
: undefined;
|
||||
if (!overrideUrl) {
|
||||
//normal path
|
||||
if (!uiSettings.backendBaseURL) {
|
||||
console.error(
|
||||
"ERROR: backendBaseURL was overridden by a setting file and missing. Setting value to 'window.origin'",
|
||||
);
|
||||
return canonicalizeBaseUrl(window.origin);
|
||||
}
|
||||
return canonicalizeBaseUrl(uiSettings.backendBaseURL);
|
||||
}
|
||||
// testing/development path
|
||||
return canonicalizeBaseUrl(overrideUrl);
|
||||
}
|
94
packages/aml-backoffice-ui/src/hooks/useCases.ts
Normal file
94
packages/aml-backoffice-ui/src/hooks/useCases.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
import { AmlExchangeBackend } from "../types.js";
|
||||
import {
|
||||
HttpResponse,
|
||||
HttpResponseOk,
|
||||
HttpResponsePaginated,
|
||||
RequestError,
|
||||
} from "@gnu-taler/web-util/browser";
|
||||
// FIX default import https://github.com/microsoft/TypeScript/issues/49189
|
||||
import _useSWR, { SWRHook } from "swr";
|
||||
import { usePublicBackend } from "./useBackend.js";
|
||||
import { AccountId, buildQuerySignature } from "../account.js";
|
||||
import { useOfficer } from "./useOfficer.js";
|
||||
const useSWR = _useSWR as unknown as SWRHook;
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
const MAX_RESULT_SIZE = PAGE_SIZE * 2 - 1;
|
||||
/**
|
||||
* FIXME: mutate result when balance change (transaction )
|
||||
* @param account
|
||||
* @param args
|
||||
* @returns
|
||||
*/
|
||||
export function useCases(
|
||||
account: AccountId,
|
||||
state: AmlExchangeBackend.AmlState,
|
||||
signature: string | undefined,
|
||||
): HttpResponsePaginated<
|
||||
AmlExchangeBackend.AmlRecords,
|
||||
AmlExchangeBackend.AmlError
|
||||
> {
|
||||
const { paginatedFetcher } = usePublicBackend();
|
||||
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
const {
|
||||
data: afterData,
|
||||
error: afterError,
|
||||
isValidating: loadingAfter,
|
||||
} = useSWR<
|
||||
HttpResponseOk<AmlExchangeBackend.AmlRecords>,
|
||||
RequestError<AmlExchangeBackend.AmlError>
|
||||
>(
|
||||
[
|
||||
`aml/${account}/decisions/${AmlExchangeBackend.AmlState[state]}`,
|
||||
page,
|
||||
PAGE_SIZE,
|
||||
signature,
|
||||
],
|
||||
paginatedFetcher,
|
||||
);
|
||||
|
||||
const [lastAfter, setLastAfter] = useState<
|
||||
HttpResponse<AmlExchangeBackend.AmlRecords, AmlExchangeBackend.AmlError>
|
||||
>({ loading: true });
|
||||
|
||||
useEffect(() => {
|
||||
if (afterData) setLastAfter(afterData);
|
||||
}, [afterData]);
|
||||
|
||||
if (afterError) {
|
||||
return afterError.cause;
|
||||
}
|
||||
|
||||
// if the query returns less that we ask, then we have reach the end or beginning
|
||||
const isReachingEnd =
|
||||
afterData && afterData.data && afterData.data.records.length < PAGE_SIZE;
|
||||
const isReachingStart = false;
|
||||
|
||||
const pagination = {
|
||||
isReachingEnd,
|
||||
isReachingStart,
|
||||
loadMore: () => {
|
||||
if (!afterData || isReachingEnd) return;
|
||||
if (afterData.data && afterData.data.records.length < MAX_RESULT_SIZE) {
|
||||
setPage(page + 1);
|
||||
}
|
||||
},
|
||||
loadMorePrev: () => {
|
||||
null;
|
||||
},
|
||||
};
|
||||
|
||||
const records = !afterData
|
||||
? []
|
||||
: ((afterData ?? lastAfter).data ?? { records: [] }).records;
|
||||
console.log("afterdata", afterData, lastAfter, records)
|
||||
if (loadingAfter) return { loading: true, data: { records } };
|
||||
if (afterData) {
|
||||
return { ok: true, data: { records }, ...pagination };
|
||||
}
|
||||
return { loading: true };
|
||||
}
|
@ -11,8 +11,8 @@ import { v1 as form_902_9e_v1 } from "../forms/902_9e.js";
|
||||
import { v1 as simplest } from "../forms/simplest.js";
|
||||
import { DocumentDuplicateIcon } from "@heroicons/react/24/solid";
|
||||
import { AbsoluteTime } from "@gnu-taler/taler-util";
|
||||
import { AmlState } from "../types.js";
|
||||
import { AmountJson, Amounts } from "@gnu-taler/taler-util";
|
||||
import { AmlExchangeBackend } from "../types.js";
|
||||
|
||||
export function AntiMoneyLaunderingForm({ number }: { number?: string }) {
|
||||
const selectedForm = Number.parseInt(number ?? "0", 10);
|
||||
@ -28,7 +28,7 @@ export function AntiMoneyLaunderingForm({ number }: { number?: string }) {
|
||||
<NiceForm
|
||||
initial={storedValue}
|
||||
form={showingFrom({
|
||||
state: AmlState.pending,
|
||||
state: AmlExchangeBackend.AmlState.pending,
|
||||
threshold: Amounts.parseOrThrow("USD:10"),
|
||||
})}
|
||||
onUpdate={() => {}}
|
||||
@ -37,7 +37,7 @@ export function AntiMoneyLaunderingForm({ number }: { number?: string }) {
|
||||
}
|
||||
|
||||
export interface State {
|
||||
state: AmlState;
|
||||
state: AmlExchangeBackend.AmlState;
|
||||
threshold: AmountJson;
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,4 @@
|
||||
import { Fragment, VNode, h } from "preact";
|
||||
import {
|
||||
AmlDecisionDetail,
|
||||
AmlDecisionDetails,
|
||||
AmlState,
|
||||
KycDetail,
|
||||
} from "../types.js";
|
||||
import {
|
||||
AbsoluteTime,
|
||||
AmountJson,
|
||||
@ -18,8 +12,9 @@ import { NiceForm } from "../NiceForm.js";
|
||||
import { FlexibleForm } from "../forms/index.js";
|
||||
import { UIFormField } from "../handlers/forms.js";
|
||||
import { Pages } from "../pages.js";
|
||||
import { AmlExchangeBackend } from "../types.js";
|
||||
|
||||
const response: AmlDecisionDetails = {
|
||||
const response: AmlExchangeBackend.AmlDecisionDetails = {
|
||||
aml_history: [
|
||||
{
|
||||
justification: "Lack of documentation",
|
||||
@ -81,7 +76,7 @@ type AmlFormEvent = {
|
||||
type: "aml-form";
|
||||
when: AbsoluteTime;
|
||||
title: TranslatedString;
|
||||
state: AmlState;
|
||||
state: AmlExchangeBackend.AmlState;
|
||||
threshold: AmountJson;
|
||||
};
|
||||
type KycCollectionEvent = {
|
||||
@ -105,8 +100,8 @@ function selectSooner(a: WithTime, b: WithTime) {
|
||||
}
|
||||
|
||||
function getEventsFromAmlHistory(
|
||||
aml: AmlDecisionDetail[],
|
||||
kyc: KycDetail[],
|
||||
aml: AmlExchangeBackend.AmlDecisionDetail[],
|
||||
kyc: AmlExchangeBackend.KycDetail[],
|
||||
): AmlEvent[] {
|
||||
const ae: AmlEvent[] = aml.map((a) => {
|
||||
return {
|
||||
@ -187,7 +182,7 @@ export function CaseDetails({ account }: { account?: string }) {
|
||||
switch (e.type) {
|
||||
case "aml-form": {
|
||||
switch (e.state) {
|
||||
case AmlState.normal: {
|
||||
case AmlExchangeBackend.AmlState.normal: {
|
||||
return (
|
||||
<div>
|
||||
<span class="inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20">
|
||||
@ -200,7 +195,7 @@ export function CaseDetails({ account }: { account?: string }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case AmlState.pending: {
|
||||
case AmlExchangeBackend.AmlState.pending: {
|
||||
return (
|
||||
<div>
|
||||
<span class="inline-flex items-center rounded-md bg-yellow-50 px-2 py-1 text-xs font-medium text-yellow-700 ring-1 ring-inset ring-green-600/20">
|
||||
@ -213,7 +208,7 @@ export function CaseDetails({ account }: { account?: string }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case AmlState.frozen: {
|
||||
case AmlExchangeBackend.AmlState.frozen: {
|
||||
return (
|
||||
<div>
|
||||
<span class="inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-green-600/20">
|
||||
@ -304,15 +299,15 @@ function ShowConsolidated({
|
||||
choices: [
|
||||
{
|
||||
label: "Frozen" as TranslatedString,
|
||||
value: AmlState.frozen,
|
||||
value: AmlExchangeBackend.AmlState.frozen,
|
||||
},
|
||||
{
|
||||
label: "Pending" as TranslatedString,
|
||||
value: AmlState.pending,
|
||||
value: AmlExchangeBackend.AmlState.pending,
|
||||
},
|
||||
{
|
||||
label: "Normal" as TranslatedString,
|
||||
value: AmlState.normal,
|
||||
value: AmlExchangeBackend.AmlState.normal,
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -361,7 +356,7 @@ function ShowConsolidated({
|
||||
|
||||
interface Consolidated {
|
||||
aml: {
|
||||
state?: AmlState;
|
||||
state?: AmlExchangeBackend.AmlState;
|
||||
threshold?: AmountJson;
|
||||
since: AbsoluteTime;
|
||||
};
|
||||
@ -421,26 +416,26 @@ export const amlStateConverter = {
|
||||
fromStringUI: parseAmlState,
|
||||
};
|
||||
|
||||
function stringifyAmlState(s: AmlState | undefined): string {
|
||||
function stringifyAmlState(s: AmlExchangeBackend.AmlState | undefined): string {
|
||||
if (s === undefined) return "";
|
||||
switch (s) {
|
||||
case AmlState.normal:
|
||||
case AmlExchangeBackend.AmlState.normal:
|
||||
return "normal";
|
||||
case AmlState.pending:
|
||||
case AmlExchangeBackend.AmlState.pending:
|
||||
return "pending";
|
||||
case AmlState.frozen:
|
||||
case AmlExchangeBackend.AmlState.frozen:
|
||||
return "frozen";
|
||||
}
|
||||
}
|
||||
|
||||
function parseAmlState(s: string | undefined): AmlState {
|
||||
function parseAmlState(s: string | undefined): AmlExchangeBackend.AmlState {
|
||||
switch (s) {
|
||||
case "normal":
|
||||
return AmlState.normal;
|
||||
return AmlExchangeBackend.AmlState.normal;
|
||||
case "pending":
|
||||
return AmlState.pending;
|
||||
return AmlExchangeBackend.AmlState.pending;
|
||||
case "frozen":
|
||||
return AmlState.frozen;
|
||||
return AmlExchangeBackend.AmlState.frozen;
|
||||
default:
|
||||
throw Error(`unknown AML state: ${s}`);
|
||||
}
|
||||
|
@ -1,15 +1,18 @@
|
||||
import { VNode, h } from "preact";
|
||||
import { Pages } from "../pages.js";
|
||||
import { AmlRecords, AmlState } from "../types.js";
|
||||
import { InputChoiceHorizontal } from "../handlers/InputChoiceHorizontal.js";
|
||||
import { createNewForm } from "../handlers/forms.js";
|
||||
import { TranslatedString } from "@gnu-taler/taler-util";
|
||||
import { amlStateConverter as amlStateConverter } from "./CaseDetails.js";
|
||||
import { VNode, h } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import { HandleAccountNotReady } from "./HandleAccountNotReady.js";
|
||||
import { createNewForm } from "../handlers/forms.js";
|
||||
import { useCases } from "../hooks/useCases.js";
|
||||
import { useOfficer } from "../hooks/useOfficer.js";
|
||||
import { Pages } from "../pages.js";
|
||||
import { AmlExchangeBackend } from "../types.js";
|
||||
import { amlStateConverter } from "./CaseDetails.js";
|
||||
import { HandleAccountNotReady } from "./HandleAccountNotReady.js";
|
||||
import { buildQuerySignature } from "../account.js";
|
||||
import { handleNotOkResult } from "../utils/errors.js";
|
||||
import { useTranslationContext } from "@gnu-taler/web-util/browser";
|
||||
|
||||
const response: AmlRecords = {
|
||||
const response: AmlExchangeBackend.AmlRecords = {
|
||||
records: [
|
||||
{
|
||||
current_state: 0,
|
||||
@ -56,7 +59,7 @@ const response: AmlRecords = {
|
||||
|
||||
function doFilter(
|
||||
list: typeof response.records,
|
||||
filter: AmlState | undefined,
|
||||
filter: AmlExchangeBackend.AmlState | undefined,
|
||||
): typeof response.records {
|
||||
if (filter === undefined) return list;
|
||||
return list.filter((r) => r.current_state === filter);
|
||||
@ -64,14 +67,27 @@ function doFilter(
|
||||
|
||||
export function Cases() {
|
||||
const officer = useOfficer();
|
||||
const { i18n } = useTranslationContext();
|
||||
if (officer.state !== "ready") {
|
||||
return <HandleAccountNotReady officer={officer} />;
|
||||
}
|
||||
const form = createNewForm<{
|
||||
state: AmlState;
|
||||
state: AmlExchangeBackend.AmlState;
|
||||
}>();
|
||||
const initial = { state: AmlState.pending };
|
||||
const [list, setList] = useState(doFilter(response.records, initial.state));
|
||||
|
||||
const signature =
|
||||
officer.state === "ready"
|
||||
? buildQuerySignature(officer.account.signingKey)
|
||||
: undefined;
|
||||
|
||||
const initial = AmlExchangeBackend.AmlState.pending;
|
||||
const [stateFilter, setStateFilter] = useState(initial);
|
||||
const list = useCases(officer.account.accountId, stateFilter, signature);
|
||||
|
||||
if (!list.ok && !list.loading) {
|
||||
return handleNotOkResult(i18n)(list);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div class="px-4 sm:px-6 lg:px-8">
|
||||
@ -85,9 +101,9 @@ export function Cases() {
|
||||
</p>
|
||||
</div>
|
||||
<form.Provider
|
||||
initialValue={initial}
|
||||
initialValue={{ state: stateFilter }}
|
||||
onUpdate={(v) => {
|
||||
setList(doFilter(response.records, v.state));
|
||||
setStateFilter(v.state ?? initial);
|
||||
}}
|
||||
onSubmit={(v) => {}}
|
||||
>
|
||||
@ -98,15 +114,15 @@ export function Cases() {
|
||||
choices={[
|
||||
{
|
||||
label: "Pending" as TranslatedString,
|
||||
value: AmlState.pending,
|
||||
value: AmlExchangeBackend.AmlState.pending,
|
||||
},
|
||||
{
|
||||
label: "Frozen" as TranslatedString,
|
||||
value: AmlState.frozen,
|
||||
value: AmlExchangeBackend.AmlState.frozen,
|
||||
},
|
||||
{
|
||||
label: "Normal" as TranslatedString,
|
||||
value: AmlState.normal,
|
||||
value: AmlExchangeBackend.AmlState.normal,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@ -114,82 +130,86 @@ export function Cases() {
|
||||
</div>
|
||||
<div class="mt-8 flow-root">
|
||||
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
||||
<Pagination />
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
|
||||
>
|
||||
Account Id
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
|
||||
>
|
||||
Status
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
|
||||
>
|
||||
Threshold
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
{list.map((r) => {
|
||||
return (
|
||||
<tr class="hover:bg-gray-100 ">
|
||||
<td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500 ">
|
||||
<div class="text-gray-900">
|
||||
<a
|
||||
href={Pages.details.url({ account: r.h_payto })}
|
||||
class="text-indigo-600 hover:text-indigo-900"
|
||||
>
|
||||
{r.h_payto}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500">
|
||||
{((state: AmlState): VNode => {
|
||||
switch (state) {
|
||||
case AmlState.normal: {
|
||||
return (
|
||||
<span class="inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20">
|
||||
Normal
|
||||
</span>
|
||||
);
|
||||
{!list.data.records.length ? (
|
||||
<div>empty result </div>
|
||||
) : (
|
||||
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
||||
<Pagination />
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
|
||||
>
|
||||
Account Id
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
|
||||
>
|
||||
Status
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
|
||||
>
|
||||
Threshold
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
{list.data.records.map((r) => {
|
||||
return (
|
||||
<tr class="hover:bg-gray-100 ">
|
||||
<td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500 ">
|
||||
<div class="text-gray-900">
|
||||
<a
|
||||
href={Pages.details.url({ account: r.h_payto })}
|
||||
class="text-indigo-600 hover:text-indigo-900"
|
||||
>
|
||||
{r.h_payto}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500">
|
||||
{((state: AmlExchangeBackend.AmlState): VNode => {
|
||||
switch (state) {
|
||||
case AmlExchangeBackend.AmlState.normal: {
|
||||
return (
|
||||
<span class="inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20">
|
||||
Normal
|
||||
</span>
|
||||
);
|
||||
}
|
||||
case AmlExchangeBackend.AmlState.pending: {
|
||||
return (
|
||||
<span class="inline-flex items-center rounded-md bg-yellow-50 px-2 py-1 text-xs font-medium text-yellow-700 ring-1 ring-inset ring-green-600/20">
|
||||
Pending
|
||||
</span>
|
||||
);
|
||||
}
|
||||
case AmlExchangeBackend.AmlState.frozen: {
|
||||
return (
|
||||
<span class="inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-green-600/20">
|
||||
Frozen
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
case AmlState.pending: {
|
||||
return (
|
||||
<span class="inline-flex items-center rounded-md bg-yellow-50 px-2 py-1 text-xs font-medium text-yellow-700 ring-1 ring-inset ring-green-600/20">
|
||||
Pending
|
||||
</span>
|
||||
);
|
||||
}
|
||||
case AmlState.frozen: {
|
||||
return (
|
||||
<span class="inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-green-600/20">
|
||||
Frozen
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
})(r.current_state)}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-5 text-sm text-gray-900">
|
||||
{r.threshold}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<Pagination />
|
||||
</div>
|
||||
})(r.current_state)}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-5 text-sm text-gray-900">
|
||||
{r.threshold}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<Pagination />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,8 +2,8 @@ import { VNode, h } from "preact";
|
||||
import { allForms } from "./AntiMoneyLaunderingForm.js";
|
||||
import { Pages } from "../pages.js";
|
||||
import { NiceForm } from "../NiceForm.js";
|
||||
import { AmlState } from "../types.js";
|
||||
import { AbsoluteTime, Amounts } from "@gnu-taler/taler-util";
|
||||
import { AmlExchangeBackend } from "../types.js";
|
||||
|
||||
export function NewFormEntry({
|
||||
account,
|
||||
@ -27,7 +27,7 @@ export function NewFormEntry({
|
||||
const initial = {
|
||||
fullName: "loggedIn_user_fullname",
|
||||
when: AbsoluteTime.now(),
|
||||
state: AmlState.pending,
|
||||
state: AmlExchangeBackend.AmlState.pending,
|
||||
threshold: Amounts.parseOrThrow("USD:10"),
|
||||
};
|
||||
return (
|
||||
|
@ -30,6 +30,9 @@ export function UnlockAccount({
|
||||
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-[480px] ">
|
||||
<div class="bg-gray-100 px-6 py-6 shadow sm:rounded-lg sm:px-12">
|
||||
<Form.Provider
|
||||
initialValue={{
|
||||
password: "welcometo.5146",
|
||||
}}
|
||||
onSubmit={async (v) => {
|
||||
try {
|
||||
await onAccountUnlocked(v.password!);
|
||||
|
35
packages/aml-backoffice-ui/src/settings.ts
Normal file
35
packages/aml-backoffice-ui/src/settings.ts
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
export interface UiSettings {
|
||||
backendBaseURL: string;
|
||||
allowRegistrations: boolean;
|
||||
uiName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Global settings for the UI.
|
||||
*/
|
||||
const defaultSettings: UiSettings = {
|
||||
backendBaseURL: "https://exchange.demo.taler.net/",
|
||||
allowRegistrations: true,
|
||||
uiName: "Taler Bank",
|
||||
};
|
||||
|
||||
export const uiSettings: UiSettings =
|
||||
"talerExchangeAmlSettings" in globalThis
|
||||
? (globalThis as any).talerExchangeAmlSettings
|
||||
: defaultSettings;
|
@ -1,81 +1,88 @@
|
||||
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[];
|
||||
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;
|
||||
|
||||
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,
|
||||
// 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;
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
43
packages/aml-backoffice-ui/src/utils/Loading.tsx
Normal file
43
packages/aml-backoffice-ui/src/utils/Loading.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { h, VNode } from "preact";
|
||||
|
||||
export function Loading(): VNode {
|
||||
return (
|
||||
<div
|
||||
class="columns is-centered is-vcentered"
|
||||
style={{
|
||||
height: "calc(100% - 3rem)",
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Spinner(): VNode {
|
||||
return (
|
||||
<div class="lds-ring">
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
);
|
||||
}
|
54
packages/aml-backoffice-ui/src/utils/QR.tsx
Normal file
54
packages/aml-backoffice-ui/src/utils/QR.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2022 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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { h, VNode } from "preact";
|
||||
import { useEffect, useRef } from "preact/hooks";
|
||||
// import qrcode from "qrcode-generator";
|
||||
|
||||
export function QR({ text }: { text: string }): VNode {
|
||||
const divRef = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
// const qr = qrcode(0, "L");
|
||||
// qr.addData(text);
|
||||
// qr.make();
|
||||
// if (divRef.current)
|
||||
// divRef.current.innerHTML = qr.createSvgTag({
|
||||
// scalable: true,
|
||||
// });
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "left",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: "50%",
|
||||
minWidth: 200,
|
||||
maxWidth: 300,
|
||||
marginRight: "auto",
|
||||
marginLeft: "auto",
|
||||
}}
|
||||
ref={divRef}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
77
packages/aml-backoffice-ui/src/utils/errors.tsx
Normal file
77
packages/aml-backoffice-ui/src/utils/errors.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
import {
|
||||
ErrorType,
|
||||
HttpResponse,
|
||||
HttpResponsePaginated,
|
||||
notifyError,
|
||||
useTranslationContext,
|
||||
} from "@gnu-taler/web-util/browser";
|
||||
import { VNode, h } from "preact";
|
||||
import { Loading } from "./Loading.js";
|
||||
import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
|
||||
import { AmlExchangeBackend } from "../types.js";
|
||||
|
||||
export function handleNotOkResult<Error extends AmlExchangeBackend.AmlError>(
|
||||
i18n: ReturnType<typeof useTranslationContext>["i18n"],
|
||||
): <T>(
|
||||
result: HttpResponsePaginated<T, Error> | HttpResponse<T, Error>,
|
||||
) => VNode {
|
||||
return function handleNotOkResult2<T>(
|
||||
result: HttpResponsePaginated<T, Error> | HttpResponse<T, Error>,
|
||||
): VNode {
|
||||
if (result.loading) return <Loading />;
|
||||
if (!result.ok) {
|
||||
switch (result.type) {
|
||||
case ErrorType.TIMEOUT: {
|
||||
notifyError(i18n.str`Request timeout, try again later.`, undefined);
|
||||
break;
|
||||
}
|
||||
case ErrorType.CLIENT: {
|
||||
if (result.status === HttpStatusCode.Unauthorized) {
|
||||
notifyError(i18n.str`Wrong credentials`, undefined);
|
||||
return <div> not authorized</div>;
|
||||
}
|
||||
const errorData = result.payload;
|
||||
notifyError(
|
||||
i18n.str`Could not load due to a client error`,
|
||||
errorData.hint as TranslatedString,
|
||||
JSON.stringify(result),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case ErrorType.SERVER: {
|
||||
notifyError(
|
||||
i18n.str`Server returned with error`,
|
||||
result.payload.hint as TranslatedString,
|
||||
JSON.stringify(result.payload),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case ErrorType.UNREADABLE: {
|
||||
notifyError(
|
||||
i18n.str`Unexpected error.`,
|
||||
`Response from ${result.info?.url} is unreadable, http status: ${result.status}` as TranslatedString,
|
||||
JSON.stringify(result),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case ErrorType.UNEXPECTED: {
|
||||
notifyError(
|
||||
i18n.str`Unexpected error.`,
|
||||
`Diagnostic from ${result.info?.url} is "${result.message}"` as TranslatedString,
|
||||
JSON.stringify(result),
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertUnreachable(result);
|
||||
}
|
||||
}
|
||||
|
||||
return <div>error</div>;
|
||||
}
|
||||
return <div />;
|
||||
};
|
||||
}
|
||||
export function assertUnreachable(x: never): never {
|
||||
throw new Error("Didn't expect to get here");
|
||||
}
|
Loading…
Reference in New Issue
Block a user