summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/aml-backoffice-ui/package.json13
-rw-r--r--packages/aml-backoffice-ui/src/App.tsx62
-rw-r--r--packages/aml-backoffice-ui/src/context/config.ts2
-rw-r--r--packages/aml-backoffice-ui/src/hooks/useCases.ts87
-rw-r--r--packages/aml-backoffice-ui/src/hooks/useOfficer.ts21
-rw-r--r--packages/aml-backoffice-ui/src/pages/Cases.tsx25
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/webhooks.ts1
-rw-r--r--packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx14
-rw-r--r--packages/taler-wallet-webextension/src/cta/Withdraw/state.ts1
-rw-r--r--pnpm-lock.yaml6
10 files changed, 129 insertions, 103 deletions
diff --git a/packages/aml-backoffice-ui/package.json b/packages/aml-backoffice-ui/package.json
index b4df017ea..9be44bd76 100644
--- a/packages/aml-backoffice-ui/package.json
+++ b/packages/aml-backoffice-ui/package.json
@@ -29,24 +29,23 @@
"history": "4.10.1",
"jed": "1.1.1",
"preact": "10.11.3",
- "swr": "2.0.3"
+ "swr": "2.2.2"
},
"devDependencies": {
- "eslint": "^8.56.0",
- "@typescript-eslint/eslint-plugin": "^6.19.0",
- "@typescript-eslint/parser": "^6.19.0",
- "eslint-config-prettier": "^9.1.0",
- "eslint-plugin-react": "^7.33.2",
-
"@gnu-taler/pogen": "^0.0.5",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.9",
"@types/chai": "^4.3.0",
"@types/history": "^4.7.8",
"@types/mocha": "^10.0.1",
+ "@typescript-eslint/eslint-plugin": "^6.19.0",
+ "@typescript-eslint/parser": "^6.19.0",
"autoprefixer": "^10.4.14",
"chai": "^4.3.6",
"esbuild": "^0.19.9",
+ "eslint": "^8.56.0",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-plugin-react": "^7.33.2",
"mocha": "^9.2.0",
"po2json": "^0.4.5",
"postcss": "^8.4.23",
diff --git a/packages/aml-backoffice-ui/src/App.tsx b/packages/aml-backoffice-ui/src/App.tsx
index d461934c0..5244476d7 100644
--- a/packages/aml-backoffice-ui/src/App.tsx
+++ b/packages/aml-backoffice-ui/src/App.tsx
@@ -6,9 +6,11 @@ import { ExchangeApiProvider } from "./context/config.js";
import { getInitialBackendBaseURL } from "./hooks/useBackend.js";
import { HashPathProvider, Router } from "./route.js";
import { Pages } from "./pages.js";
+import { SWRConfig } from "swr";
-const pageList = Object.values(Pages);
+const WITH_LOCAL_STORAGE_CACHE = false;
+const pageList = Object.values(Pages);
export function App(): VNode {
const baseUrl = getInitialBackendBaseURL();
@@ -16,17 +18,57 @@ export function App(): VNode {
<TranslationProvider source={{}}>
<ExchangeApiProvider baseUrl={baseUrl} frameOnError={ExchangeAmlFrame}>
<HashPathProvider>
- <ExchangeAmlFrame>
- <Router
- pageList={pageList}
- onNotFound={() => {
- window.location.href = Pages.cases.url
- return <div>not found</div>;
- }}
- />
- </ExchangeAmlFrame>
+ <SWRConfig
+ value={{
+ provider: WITH_LOCAL_STORAGE_CACHE
+ ? localStorageProvider
+ : undefined,
+ // normally, do not revalidate
+ revalidateOnFocus: false,
+ revalidateOnReconnect: false,
+ revalidateIfStale: false,
+ revalidateOnMount: undefined,
+ focusThrottleInterval: undefined,
+
+ // normally, do not refresh
+ refreshInterval: undefined,
+ dedupingInterval: 2000,
+ refreshWhenHidden: false,
+ refreshWhenOffline: false,
+
+ // ignore errors
+ shouldRetryOnError: false,
+ errorRetryCount: 0,
+ errorRetryInterval: undefined,
+
+ // do not go to loading again if already has data
+ keepPreviousData: true,
+ }}
+ >
+
+ <ExchangeAmlFrame>
+ <Router
+ pageList={pageList}
+ onNotFound={() => {
+ window.location.href = Pages.cases.url
+ return <div>not found</div>;
+ }}
+ />
+ </ExchangeAmlFrame>
+ </SWRConfig>
</HashPathProvider>
</ExchangeApiProvider>
</TranslationProvider>
);
}
+
+
+function localStorageProvider(): Map<unknown, unknown> {
+ const map = new Map(JSON.parse(localStorage.getItem("app-cache") || "[]"));
+
+ window.addEventListener("beforeunload", () => {
+ const appCache = JSON.stringify(Array.from(map.entries()));
+ localStorage.setItem("app-cache", appCache);
+ });
+ return map;
+}
diff --git a/packages/aml-backoffice-ui/src/context/config.ts b/packages/aml-backoffice-ui/src/context/config.ts
index 7004225eb..42f73428a 100644
--- a/packages/aml-backoffice-ui/src/context/config.ts
+++ b/packages/aml-backoffice-ui/src/context/config.ts
@@ -67,7 +67,7 @@ export const ExchangeApiProvider = ({
.then((resp) => {
if (resp.type === "fail") {
setChecked({ type: "error", error: TalerError.fromUncheckedDetail(resp.detail) });
- }else if (api.isCompatible(resp.body.version)) {
+ } else if (api.isCompatible(resp.body.version)) {
setChecked({ type: "ok", config: resp.body });
} else {
setChecked({ type: "incompatible", result: resp.body, supported: api.PROTOCOL_VERSION })
diff --git a/packages/aml-backoffice-ui/src/hooks/useCases.ts b/packages/aml-backoffice-ui/src/hooks/useCases.ts
index 68deb7db9..2bc9b5f0f 100644
--- a/packages/aml-backoffice-ui/src/hooks/useCases.ts
+++ b/packages/aml-backoffice-ui/src/hooks/useCases.ts
@@ -1,14 +1,18 @@
import { useState } from "preact/hooks";
// FIX default import https://github.com/microsoft/TypeScript/issues/49189
-import { AmountString, HttpStatusCode, OfficerAccount, OperationFail, TalerExchangeApi, TalerExchangeResultByMethod, TalerHttpError } from "@gnu-taler/taler-util";
+import { OfficerAccount, OfficerId, OperationOk, TalerExchangeResultByMethod, TalerHttpError, decodeCrock, encodeCrock } from "@gnu-taler/taler-util";
import _useSWR, { SWRHook } from "swr";
import { useExchangeApiContext } from "../context/config.js";
-import { useOfficer } from "./useOfficer.js";
import { AmlExchangeBackend } from "../utils/types.js";
+import { useOfficer } from "./useOfficer.js";
const useSWR = _useSWR as unknown as SWRHook;
-const PAGE_SIZE = 10;
+export const PAGINATED_LIST_SIZE = 10;
+// when doing paginated request, ask for one more
+// and use it to know if there are more to request
+export const PAGINATED_LIST_REQUEST = PAGINATED_LIST_SIZE + 1;
+
/**
* FIXME: mutate result when balance change (transaction )
* @param account
@@ -24,69 +28,56 @@ export function useCases(state: AmlExchangeBackend.AmlState) {
async function fetcher([officer, state, offset]: [OfficerAccount, AmlExchangeBackend.AmlState, string | undefined]) {
return await api.getDecisionsByState(officer, state, {
- order: "asc", offset, limit: PAGE_SIZE + 1
+ order: "asc", offset, limit: PAGINATED_LIST_REQUEST
})
}
const { data, error } = useSWR<TalerExchangeResultByMethod<"getDecisionsByState">, TalerHttpError>(
- !session ? undefined : [session, state, offset],
+ !session ? undefined : [session, state, offset, "getDecisionsByState"],
fetcher,
);
- // const [lastAfter, setLastAfter] = useState<
- // HttpResponse<AmlExchangeBackend.AmlRecords, AmlExchangeBackend.AmlError>
- // >({ loading: true });
+ if (error) return error;
+ if (data === undefined) return undefined;
+ if (data.type !== "ok") return data;
- // useEffect(() => {
- // if (afterData) setLastAfter(afterData);
- // }, [afterData]);
+ return buildPaginatedResult(data.body.records, offset, setOffset, (d) => String(d.rowid));
+}
- // if (afterError) {
- // return afterError.cause;
- // }
+type PaginatedResult<T> = OperationOk<T> & {
+ isLastPage: boolean;
+ isFirstPage: boolean;
+ loadNext(): void;
+ loadFirst(): void;
+}
+
+//TODO: consider sending this to web-util
+export function buildPaginatedResult<R, OffId>(data: R[], offset: OffId | undefined, setOffset: (o: OffId | undefined) => void, getId: (r: R) => OffId): PaginatedResult<R[]> {
- // if the query returns less that we ask, then we have reach the end or beginning
- const isLastPage =
- data && data.type === "ok" && data.body.records.length <= PAGE_SIZE;
- const isFirstPage = !offset;
+ const isLastPage = data.length < PAGINATED_LIST_REQUEST;
+ const isFirstPage = offset === undefined;
- const pagination = {
+ const result = structuredClone(data);
+ if (result.length == PAGINATED_LIST_REQUEST) {
+ result.pop();
+ }
+ return {
+ type: "ok",
+ body: result,
isLastPage,
isFirstPage,
- loadMore: () => {
- if (isLastPage || data?.type !== "ok") return;
- const list = data.body.records
- setOffset(String(list[list.length - 1].rowid));
+ loadNext: () => {
+ if (!result.length) return;
+ const id = getId(result[result.length - 1])
+ setOffset(id);
},
- reset: () => {
- setOffset(undefined)
+ loadFirst: () => {
+ setOffset(undefined);
},
};
-
- // const public_accountslist = data?.type !== "ok" ? [] : data.body.public_accounts;
- if (!session) {
- return {
- data: {
- type: "fail",
- case: HttpStatusCode.Unauthorized,
- detail: {}
- } as OperationFail<never>
- }
- }
-
- if (data) {
- if (data.type === "fail") {
- return { data }
- }
- const records = isLastPage ? data.body.records : removeLastElement(data.body.records)
- return { data: { type: "ok" as const, body: { records } }, pagination }
- }
- if (error) {
- return error;
- }
- return undefined;
}
+
function removeLastElement<T>(list: Array<T>): Array<T> {
if (list.length === 0) {
return list;
diff --git a/packages/aml-backoffice-ui/src/hooks/useOfficer.ts b/packages/aml-backoffice-ui/src/hooks/useOfficer.ts
index fe989f3eb..1bf2b308b 100644
--- a/packages/aml-backoffice-ui/src/hooks/useOfficer.ts
+++ b/packages/aml-backoffice-ui/src/hooks/useOfficer.ts
@@ -8,19 +8,17 @@ import {
buildCodecForObject,
codecForAbsoluteTime,
codecForString,
- codecOptional,
createNewOfficerAccount,
decodeCrock,
encodeCrock,
- unlockOfficerAccount,
+ unlockOfficerAccount
} from "@gnu-taler/taler-util";
import {
buildStorageKey,
- useLocalStorage,
- useMemoryStorage,
+ useLocalStorage
} from "@gnu-taler/web-util/browser";
import { useMemo } from "preact/hooks";
-import { useExchangeApiContext, useMaybeExchangeApiContext } from "../context/config.js";
+import { useMaybeExchangeApiContext } from "../context/config.js";
export interface Officer {
account: LockedAccount;
@@ -66,7 +64,6 @@ interface OfficerReady {
const OFFICER_KEY = buildStorageKey("officer", codecForOfficer());
const DEV_ACCOUNT_KEY = buildStorageKey("account-dev", codecForOfficerAccount());
-const ACCOUNT_KEY = "account";
export function useOfficer(): OfficerState {
const exchangeContext = useMaybeExchangeApiContext();
@@ -74,18 +71,18 @@ export function useOfficer(): OfficerState {
const accountStorage = useLocalStorage(DEV_ACCOUNT_KEY);
const account = useMemo(() => {
if (!accountStorage.value) return undefined
+
return {
id: accountStorage.value.id as OfficerId,
signingKey: decodeCrock(accountStorage.value.strKey) as SigningKey
}
- }, [accountStorage.value])
-
-
- // const accountStorage = useMemoryStorage<OfficerAccount>(ACCOUNT_KEY);
- // const account = accountStorage.value;
+ }, [accountStorage.value?.id, accountStorage.value?.strKey])
const officerStorage = useLocalStorage(OFFICER_KEY);
- const officer = officerStorage.value;
+ const officer = useMemo(() => {
+ if (!officerStorage.value) return undefined
+ return officerStorage.value
+ }, [officerStorage.value?.account, officerStorage.value?.when.t_ms])
if (officer === undefined) {
return {
diff --git a/packages/aml-backoffice-ui/src/pages/Cases.tsx b/packages/aml-backoffice-ui/src/pages/Cases.tsx
index faef0ca54..061286f51 100644
--- a/packages/aml-backoffice-ui/src/pages/Cases.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Cases.tsx
@@ -70,7 +70,7 @@ export function CasesUI({
onUpdate={(v) => {
onChangeFilter(v.state ?? filter);
}}
- onSubmit={(_v) => {}}
+ onSubmit={(_v) => { }}
>
<form.InputChoiceHorizontal
name="state"
@@ -182,6 +182,7 @@ export function CasesUI({
}
export function Cases() {
+
const [stateFilter, setStateFilter] = useState(
AmlExchangeBackend.AmlState.pending,
);
@@ -195,33 +196,23 @@ export function Cases() {
return <ErrorLoading error={list} />;
}
- if (list.data.type === "fail") {
- switch (list.data.case) {
+ if (list.type === "fail") {
+ switch (list.case) {
case HttpStatusCode.Unauthorized:
case HttpStatusCode.Forbidden:
case HttpStatusCode.NotFound:
case HttpStatusCode.Conflict:
return <Officer />;
default:
- assertUnreachable(list.data);
+ assertUnreachable(list);
}
}
- const { records } = list.data.body;
-
return (
<CasesUI
- records={records}
- onFirstPage={
- list.pagination && !list.pagination.isFirstPage
- ? list.pagination.reset
- : undefined
- }
- onNext={
- list.pagination && !list.pagination.isLastPage
- ? list.pagination.loadMore
- : undefined
- }
+ records={list.body}
+ onFirstPage={list.isFirstPage ? undefined : list.loadFirst}
+ onNext={list.isLastPage ? undefined : list.loadNext}
filter={stateFilter}
onChangeFilter={setStateFilter}
/>
diff --git a/packages/merchant-backoffice-ui/src/hooks/webhooks.ts b/packages/merchant-backoffice-ui/src/hooks/webhooks.ts
index ff0d8b8a9..df53c06bc 100644
--- a/packages/merchant-backoffice-ui/src/hooks/webhooks.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/webhooks.ts
@@ -16,7 +16,6 @@
import {
useMerchantApiContext
} from "@gnu-taler/web-util/browser";
-import { useState } from "preact/hooks";
import { PAGINATED_LIST_REQUEST } from "../utils/constants.js";
// FIX default import https://github.com/microsoft/TypeScript/issues/49189
diff --git a/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
index 3bcf2d6ba..007c840c6 100644
--- a/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
+++ b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
@@ -42,14 +42,19 @@ export interface BankDetailsProps {
export function BankDetailsByPaytoType({
subject,
amount,
- accounts,
+ accounts: unsortedAccounts,
}: BankDetailsProps): VNode {
const { i18n } = useTranslationContext();
const [index, setIndex] = useState(0)
- // const [currency, setCurrency] = useState(amount.currency)
- if (!accounts.length) {
+
+ if (!unsortedAccounts.length) {
return <div>the exchange account list is empty</div>
}
+
+ const accounts = unsortedAccounts.sort((a, b) => {
+ return (b.priority ?? 0) - (a.priority ?? 0)
+ })
+
const selectedAccount = accounts[index];
const altCurrency = selectedAccount.currencySpecification?.name
@@ -83,12 +88,13 @@ export function BankDetailsByPaytoType({
{accounts.length > 1 ?
<Fragment>
{accounts.map((ac, acIdx) => {
+ const accountLabel = ac.bankLabel ?? `Account #${acIdx + 1}`
return <Button key={acIdx} variant={acIdx === index ? "contained" : "outlined"}
onClick={async () => {
setIndex(acIdx)
}}
>
- <i18n.Translate>Account #{acIdx+1} ({ac.currencySpecification?.name ?? amount.currency})</i18n.Translate>
+ {accountLabel} ({ac.currencySpecification?.name ?? amount.currency})
</Button>
})}
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts
index 51859d6a7..7486d5f97 100644
--- a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts
+++ b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts
@@ -454,6 +454,7 @@ function exchangeSelectionState(
altCurrencies.length === 0
? []
: [toBeReceived.currency, ...altCurrencies];
+
const convAccount = amountHook.response.accounts.find((c) => {
return (
c.currencySpecification &&
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6f53c7d90..b1c5511c8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,4 +1,4 @@
-lockfileVersion: '6.0'
+lockfileVersion: '6.1'
settings:
autoInstallPeers: true
@@ -57,8 +57,8 @@ importers:
specifier: 10.11.3
version: 10.11.3
swr:
- specifier: 2.0.3
- version: 2.0.3(react@18.2.0)
+ specifier: 2.2.2
+ version: 2.2.2(react@18.2.0)
devDependencies:
'@gnu-taler/pogen':
specifier: ^0.0.5