/* This file is part of GNU Taler (C) 2022-2024 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 */ import { AccessToken, OperationOk, TalerCoreBankResultByMethod, TalerHttpError, WithdrawalOperationStatus, } from "@gnu-taler/taler-util"; import { useEffect, useState } from "preact/hooks"; import { useSessionState } from "./session.js"; // FIX default import https://github.com/microsoft/TypeScript/issues/49189 import _useSWR, { SWRHook, mutate } from "swr"; import { useBankCoreApiContext } from "@gnu-taler/web-util/browser"; import { PAGINATED_LIST_REQUEST } from "../utils.js"; const useSWR = _useSWR as unknown as SWRHook; export interface InstanceTemplateFilter { // FIXME: add filter to the template list position?: string; } export function revalidateAccountDetails() { return mutate( (key) => Array.isArray(key) && key[key.length - 1] === "getAccount", undefined, { revalidate: true }, ); } export function useAccountDetails(account: string) { const { state: credentials } = useSessionState(); const { lib: { bank: api }, } = useBankCoreApiContext(); async function fetcher([username, token]: [string, AccessToken]) { return await api.getAccount({ username, token }); } const token = credentials.status !== "loggedIn" ? undefined : credentials.token; const { data, error } = useSWR< TalerCoreBankResultByMethod<"getAccount">, TalerHttpError >([account, token, "getAccount"], fetcher, {}); if (data) return data; if (error) return error; return undefined; } export function revalidateWithdrawalDetails() { return mutate( (key) => Array.isArray(key) && key[key.length - 1] === "getWithdrawalById", undefined, { revalidate: true }, ); } export function useWithdrawalDetails(wid: string) { const { lib: { bank: api }, } = useBankCoreApiContext(); const [latestStatus, setLatestStatus] = useState(); async function fetcher([wid, old_state]: [ string, WithdrawalOperationStatus | undefined, ]) { return await api.getWithdrawalById( wid, old_state === undefined ? undefined : { old_state, timeoutMs: 15000 }, ); } const { data, error } = useSWR< TalerCoreBankResultByMethod<"getWithdrawalById">, TalerHttpError >([wid, latestStatus, "getWithdrawalById"], fetcher, { refreshInterval: 3000, refreshWhenHidden: false, revalidateOnFocus: false, revalidateOnReconnect: false, refreshWhenOffline: false, errorRetryCount: 0, errorRetryInterval: 1, shouldRetryOnError: false, keepPreviousData: true, }); const currentStatus = data !== undefined && data.type === "ok" ? data.body.status : undefined; useEffect(() => { if (currentStatus !== undefined && currentStatus !== latestStatus) { setLatestStatus(currentStatus); } }, [currentStatus]); if (data) return data; if (error) return error; return undefined; } export function revalidateTransactionDetails() { return mutate( (key) => Array.isArray(key) && key[key.length - 1] === "getTransactionById", undefined, { revalidate: true }, ); } export function useTransactionDetails(account: string, tid: number) { const { state: credentials } = useSessionState(); const token = credentials.status !== "loggedIn" ? undefined : credentials.token; const { lib: { bank: api }, } = useBankCoreApiContext(); async function fetcher([username, token, txid]: [ string, AccessToken, number, ]) { return await api.getTransactionById({ username, token }, txid); } const { data, error } = useSWR< TalerCoreBankResultByMethod<"getTransactionById">, TalerHttpError >([account, token, tid, "getTransactionById"], fetcher, { refreshInterval: 0, refreshWhenHidden: false, revalidateOnFocus: false, revalidateOnReconnect: false, refreshWhenOffline: false, errorRetryCount: 0, errorRetryInterval: 1, shouldRetryOnError: false, keepPreviousData: true, }); if (data) return data; if (error) return error; return undefined; } export async function revalidatePublicAccounts() { return mutate( (key) => Array.isArray(key) && key[key.length - 1] === "getPublicAccounts", undefined, { revalidate: true }, ); } export function usePublicAccounts( filterAccount: string | undefined, initial?: number, ) { const [offset, setOffset] = useState(initial); const { lib: { bank: api }, } = useBankCoreApiContext(); async function fetcher([account, txid]: [ string | undefined, number | undefined, ]) { return await api.getPublicAccounts( { account }, { limit: PAGINATED_LIST_REQUEST, offset: txid ? String(txid) : undefined, order: "asc", }, ); } const { data, error } = useSWR< TalerCoreBankResultByMethod<"getPublicAccounts">, TalerHttpError >([filterAccount, offset, "getPublicAccounts"], fetcher, { refreshInterval: 0, refreshWhenHidden: false, revalidateOnFocus: false, revalidateOnReconnect: false, refreshWhenOffline: false, errorRetryCount: 0, errorRetryInterval: 1, shouldRetryOnError: false, keepPreviousData: true, }); if (error) return error; if (data === undefined) return undefined; // if (data.type !== "ok") return data; //TODO: row_id should not be optional return buildPaginatedResult( data.body.public_accounts, offset, setOffset, (d) => d.row_id ?? 0, ); } type PaginatedResult = OperationOk & { isLastPage: boolean; isFirstPage: boolean; loadNext(): void; loadFirst(): void; }; //TODO: consider sending this to web-util export function buildPaginatedResult( data: DataType[], offset: OffsetId | undefined, setOffset: (o: OffsetId | undefined) => void, getId: (r: DataType) => OffsetId, ): PaginatedResult { const isLastPage = data.length < PAGINATED_LIST_REQUEST; const isFirstPage = offset === undefined; const result = structuredClone(data); if (result.length == PAGINATED_LIST_REQUEST) { //do now show the last element, used to know if this is the last page result.pop(); } return { type: "ok", body: result, isLastPage, isFirstPage, loadNext: () => { if (!result.length) return; const id = getId(result[result.length - 1]); setOffset(id); }, loadFirst: () => { setOffset(undefined); }, }; } export function revalidateTransactions() { return mutate( (key) => Array.isArray(key) && key[key.length - 1] === "getTransactions", undefined, { revalidate: true }, ); } export function useTransactions(account: string, initial?: number) { const { state: credentials } = useSessionState(); const token = credentials.status !== "loggedIn" ? undefined : credentials.token; const [offset, setOffset] = useState(initial); const { lib: { bank: api }, } = useBankCoreApiContext(); async function fetcher([username, token, txid]: [ string, AccessToken, number | undefined, ]) { return await api.getTransactions( { username, token }, { limit: PAGINATED_LIST_REQUEST, offset: txid ? String(txid) : undefined, order: "dec", }, ); } const { data, error } = useSWR< TalerCoreBankResultByMethod<"getTransactions">, TalerHttpError >([account, token, offset, "getTransactions"], fetcher, { refreshInterval: 0, refreshWhenHidden: false, refreshWhenOffline: false, // revalidateOnMount: false, revalidateIfStale: false, revalidateOnFocus: false, revalidateOnReconnect: false, }); if (error) return error; if (data === undefined) return undefined; if (data.type !== "ok") return data; return buildPaginatedResult( data.body.transactions, offset, setOffset, (d) => d.row_id, ); }