summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2023-07-20 17:01:35 -0300
committerSebastian <sebasjm@gmail.com>2023-07-20 17:01:35 -0300
commit2335c3418cfbcc8a0196f0f161bab31ade99acb2 (patch)
tree76edf53acc5810ecfe215f6f99f755833a492af8
parent24d05d87ecfb880b174e90a3d585240d190eaefe (diff)
downloadwallet-core-2335c3418cfbcc8a0196f0f161bab31ade99acb2.tar.gz
wallet-core-2335c3418cfbcc8a0196f0f161bab31ade99acb2.tar.bz2
wallet-core-2335c3418cfbcc8a0196f0f161bab31ade99acb2.zip
make signed request to exchange
-rw-r--r--packages/aml-backoffice-ui/src/account.ts28
-rw-r--r--packages/aml-backoffice-ui/src/forms/902_11e.ts9
-rw-r--r--packages/aml-backoffice-ui/src/forms/902_12e.ts9
-rw-r--r--packages/aml-backoffice-ui/src/forms/902_13e.ts9
-rw-r--r--packages/aml-backoffice-ui/src/forms/902_15e.ts9
-rw-r--r--packages/aml-backoffice-ui/src/forms/902_1e.ts11
-rw-r--r--packages/aml-backoffice-ui/src/forms/902_4e.ts15
-rw-r--r--packages/aml-backoffice-ui/src/forms/902_5e.ts9
-rw-r--r--packages/aml-backoffice-ui/src/forms/902_9e.ts9
-rw-r--r--packages/aml-backoffice-ui/src/forms/simplest.ts18
-rw-r--r--packages/aml-backoffice-ui/src/hooks/useBackend.ts81
-rw-r--r--packages/aml-backoffice-ui/src/hooks/useCases.ts94
-rw-r--r--packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx6
-rw-r--r--packages/aml-backoffice-ui/src/pages/CaseDetails.tsx45
-rw-r--r--packages/aml-backoffice-ui/src/pages/Cases.tsx204
-rw-r--r--packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx4
-rw-r--r--packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx3
-rw-r--r--packages/aml-backoffice-ui/src/settings.ts35
-rw-r--r--packages/aml-backoffice-ui/src/types.ts167
-rw-r--r--packages/aml-backoffice-ui/src/utils/Loading.tsx43
-rw-r--r--packages/aml-backoffice-ui/src/utils/QR.tsx54
-rw-r--r--packages/aml-backoffice-ui/src/utils/errors.tsx77
22 files changed, 665 insertions, 274 deletions
diff --git a/packages/aml-backoffice-ui/src/account.ts b/packages/aml-backoffice-ui/src/account.ts
index bd3c2003e..1c8cd7f53 100644
--- a/packages/aml-backoffice-ui/src/account.ts
+++ b/packages/aml-backoffice-ui/src/account.ts
@@ -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 };
diff --git a/packages/aml-backoffice-ui/src/forms/902_11e.ts b/packages/aml-backoffice-ui/src/forms/902_11e.ts
index 24df6a44c..a91e7a866 100644
--- a/packages/aml-backoffice-ui/src/forms/902_11e.ts
+++ b/packages/aml-backoffice-ui/src/forms/902_11e.ts
@@ -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> => ({
diff --git a/packages/aml-backoffice-ui/src/forms/902_12e.ts b/packages/aml-backoffice-ui/src/forms/902_12e.ts
index c80539511..ea95b494b 100644
--- a/packages/aml-backoffice-ui/src/forms/902_12e.ts
+++ b/packages/aml-backoffice-ui/src/forms/902_12e.ts
@@ -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> => ({
diff --git a/packages/aml-backoffice-ui/src/forms/902_13e.ts b/packages/aml-backoffice-ui/src/forms/902_13e.ts
index 63870f00a..666cf35d4 100644
--- a/packages/aml-backoffice-ui/src/forms/902_13e.ts
+++ b/packages/aml-backoffice-ui/src/forms/902_13e.ts
@@ -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> => ({
diff --git a/packages/aml-backoffice-ui/src/forms/902_15e.ts b/packages/aml-backoffice-ui/src/forms/902_15e.ts
index 19a16d3f2..502cee8e5 100644
--- a/packages/aml-backoffice-ui/src/forms/902_15e.ts
+++ b/packages/aml-backoffice-ui/src/forms/902_15e.ts
@@ -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> => ({
diff --git a/packages/aml-backoffice-ui/src/forms/902_1e.ts b/packages/aml-backoffice-ui/src/forms/902_1e.ts
index 654085443..167d1ac19 100644
--- a/packages/aml-backoffice-ui/src/forms/902_1e.ts
+++ b/packages/aml-backoffice-ui/src/forms/902_1e.ts
@@ -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> => ({
diff --git a/packages/aml-backoffice-ui/src/forms/902_4e.ts b/packages/aml-backoffice-ui/src/forms/902_4e.ts
index f77a2f63a..cecd74390 100644
--- a/packages/aml-backoffice-ui/src/forms/902_4e.ts
+++ b/packages/aml-backoffice-ui/src/forms/902_4e.ts
@@ -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> => ({
diff --git a/packages/aml-backoffice-ui/src/forms/902_5e.ts b/packages/aml-backoffice-ui/src/forms/902_5e.ts
index bd27b7a7f..501a3b23c 100644
--- a/packages/aml-backoffice-ui/src/forms/902_5e.ts
+++ b/packages/aml-backoffice-ui/src/forms/902_5e.ts
@@ -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> => ({
diff --git a/packages/aml-backoffice-ui/src/forms/902_9e.ts b/packages/aml-backoffice-ui/src/forms/902_9e.ts
index e79597bfb..04f0a1572 100644
--- a/packages/aml-backoffice-ui/src/forms/902_9e.ts
+++ b/packages/aml-backoffice-ui/src/forms/902_9e.ts
@@ -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> => ({
diff --git a/packages/aml-backoffice-ui/src/forms/simplest.ts b/packages/aml-backoffice-ui/src/forms/simplest.ts
index 7eda03fef..023c1765f 100644
--- a/packages/aml-backoffice-ui/src/forms/simplest.ts
+++ b/packages/aml-backoffice-ui/src/forms/simplest.ts
@@ -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,
},
],
diff --git a/packages/aml-backoffice-ui/src/hooks/useBackend.ts b/packages/aml-backoffice-ui/src/hooks/useBackend.ts
new file mode 100644
index 000000000..e68efb2e3
--- /dev/null
+++ b/packages/aml-backoffice-ui/src/hooks/useBackend.ts
@@ -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);
+}
diff --git a/packages/aml-backoffice-ui/src/hooks/useCases.ts b/packages/aml-backoffice-ui/src/hooks/useCases.ts
new file mode 100644
index 000000000..04b7c383b
--- /dev/null
+++ b/packages/aml-backoffice-ui/src/hooks/useCases.ts
@@ -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 };
+}
diff --git a/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx b/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx
index 713c0d7c1..c3fb7dafe 100644
--- a/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx
+++ b/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx
@@ -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;
}
diff --git a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
index e5fb8eaba..d02d8b395 100644
--- a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
+++ b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
@@ -1,11 +1,5 @@
import { Fragment, VNode, h } from "preact";
import {
- AmlDecisionDetail,
- AmlDecisionDetails,
- AmlState,
- KycDetail,
-} from "../types.js";
-import {
AbsoluteTime,
AmountJson,
Amounts,
@@ -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}`);
}
diff --git a/packages/aml-backoffice-ui/src/pages/Cases.tsx b/packages/aml-backoffice-ui/src/pages/Cases.tsx
index 28b9d2a88..d96e7a90c 100644
--- a/packages/aml-backoffice-ui/src/pages/Cases.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Cases.tsx
@@ -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>
- );
- }
- 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>
- );
+ {!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>
+ );
+ }
}
- }
- })(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>
diff --git a/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx b/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx
index fdb255701..bbd04daee 100644
--- a/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx
+++ b/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx
@@ -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 (
diff --git a/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx b/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx
index 2ebac0718..39f8addd3 100644
--- a/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx
+++ b/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx
@@ -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!);
diff --git a/packages/aml-backoffice-ui/src/settings.ts b/packages/aml-backoffice-ui/src/settings.ts
new file mode 100644
index 000000000..2897874a2
--- /dev/null
+++ b/packages/aml-backoffice-ui/src/settings.ts
@@ -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;
diff --git a/packages/aml-backoffice-ui/src/types.ts b/packages/aml-backoffice-ui/src/types.ts
index 1197b6b35..104d938b3 100644
--- a/packages/aml-backoffice-ui/src/types.ts
+++ b/packages/aml-backoffice-ui/src/types.ts
@@ -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[];
-
- // 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,
+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,
+ }
}
diff --git a/packages/aml-backoffice-ui/src/utils/Loading.tsx b/packages/aml-backoffice-ui/src/utils/Loading.tsx
new file mode 100644
index 000000000..7cbdad681
--- /dev/null
+++ b/packages/aml-backoffice-ui/src/utils/Loading.tsx
@@ -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>
+ );
+}
diff --git a/packages/aml-backoffice-ui/src/utils/QR.tsx b/packages/aml-backoffice-ui/src/utils/QR.tsx
new file mode 100644
index 000000000..1dc1712b7
--- /dev/null
+++ b/packages/aml-backoffice-ui/src/utils/QR.tsx
@@ -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>
+ );
+}
diff --git a/packages/aml-backoffice-ui/src/utils/errors.tsx b/packages/aml-backoffice-ui/src/utils/errors.tsx
new file mode 100644
index 000000000..b67d61a5f
--- /dev/null
+++ b/packages/aml-backoffice-ui/src/utils/errors.tsx
@@ -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");
+}