/* 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 */ import useSWR from "swr"; import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js"; import { useEffect, useState } from "preact/hooks"; import { HttpError, HttpResponse, HttpResponseOk, HttpResponsePaginated, } from "@gnu-taler/web-util/lib/index.browser"; import { useAuthenticatedBackend, useMatchMutate, usePublicBackend } from "./backend.js"; import { useBackendContext } from "../context/backend.js"; export function useAccessAPI(): AccessAPI { const mutateAll = useMatchMutate(); const { request } = useAuthenticatedBackend(); const { state } = useBackendContext() if (state.status === "loggedOut") { throw Error("access-api can't be used when the user is not logged In") } const account = state.username const createWithdrawal = async ( data: SandboxBackend.Access.BankAccountCreateWithdrawalRequest, ): Promise> => { const res = await request(`access-api/accounts/${account}/withdrawals`, { method: "POST", data, contentType: "json" }); return res; }; const abortWithdrawal = async ( id: string, ): Promise> => { const res = await request(`access-api/accounts/${account}/withdrawals/${id}`, { method: "POST", contentType: "json" }); await mutateAll(/.*accounts\/.*\/withdrawals\/.*/); return res; }; const confirmWithdrawal = async ( id: string, ): Promise> => { const res = await request(`access-api/accounts/${account}/withdrawals/${id}`, { method: "POST", contentType: "json" }); await mutateAll(/.*accounts\/.*\/withdrawals\/.*/); return res; }; const createTransaction = async ( data: SandboxBackend.Access.CreateBankAccountTransactionCreate ): Promise> => { const res = await request(`access-api/accounts/${account}/transactions`, { method: "POST", data, contentType: "json" }); await mutateAll(/.*accounts\/.*\/transactions.*/); return res; }; const deleteAccount = async ( ): Promise> => { const res = await request(`access-api/accounts/${account}`, { method: "DELETE", contentType: "json" }); await mutateAll(/.*accounts\/.*/); return res; }; return { abortWithdrawal, confirmWithdrawal, createWithdrawal, createTransaction, deleteAccount }; } export function useTestingAPI(): TestingAPI { const mutateAll = useMatchMutate(); const { request: noAuthRequest } = usePublicBackend(); const register = async ( data: SandboxBackend.Access.BankRegistrationRequest ): Promise> => { const res = await noAuthRequest(`access-api/testing/register`, { method: "POST", data, contentType: "json" }); await mutateAll(/.*accounts\/.*/); return res; }; return { register }; } export interface TestingAPI { register: ( data: SandboxBackend.Access.BankRegistrationRequest ) => Promise>; } export interface AccessAPI { createWithdrawal: ( data: SandboxBackend.Access.BankAccountCreateWithdrawalRequest, ) => Promise>; abortWithdrawal: ( wid: string, ) => Promise>; confirmWithdrawal: ( wid: string ) => Promise>; createTransaction: ( data: SandboxBackend.Access.CreateBankAccountTransactionCreate ) => Promise>; deleteAccount: () => Promise>; } export interface InstanceTemplateFilter { //FIXME: add filter to the template list position?: string; } export function useAccountDetails(account: string): HttpResponse { const { fetcher } = useAuthenticatedBackend(); const { data, error } = useSWR< HttpResponseOk, HttpError >([`access-api/accounts/${account}`], 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 { loading: true }; } // FIXME: should poll export function useWithdrawalDetails(account: string, wid: string): HttpResponse { const { fetcher } = useAuthenticatedBackend(); const { data, error } = useSWR< HttpResponseOk, HttpError >([`access-api/accounts/${account}/withdrawals/${wid}`], fetcher, { refreshInterval: 1000, refreshWhenHidden: false, revalidateOnFocus: false, revalidateOnReconnect: false, refreshWhenOffline: false, errorRetryCount: 0, errorRetryInterval: 1, shouldRetryOnError: false, keepPreviousData: true, }); // if (isValidating) return { loading: true, data: data?.data }; if (data) return data; if (error) return error; return { loading: true }; } export function useTransactionDetails(account: string, tid: string): HttpResponse { const { fetcher } = useAuthenticatedBackend(); const { data, error } = useSWR< HttpResponseOk, HttpError >([`access-api/accounts/${account}/transactions/${tid}`], fetcher, { refreshInterval: 0, refreshWhenHidden: false, revalidateOnFocus: false, revalidateOnReconnect: false, refreshWhenOffline: false, errorRetryCount: 0, errorRetryInterval: 1, shouldRetryOnError: false, keepPreviousData: true, }); // if (isValidating) return { loading: true, data: data?.data }; if (data) return data; if (error) return error; return { loading: true }; } interface PaginationFilter { page: number, } export function usePublicAccounts( args?: PaginationFilter, ): HttpResponsePaginated { const { paginatedFetcher } = usePublicBackend(); const [page, setPage] = useState(1); const { data: afterData, error: afterError, isValidating: loadingAfter, } = useSWR< HttpResponseOk, HttpError >([`public-accounts`, args?.page, PAGE_SIZE], paginatedFetcher); const [lastAfter, setLastAfter] = useState< HttpResponse >({ loading: true }); useEffect(() => { if (afterData) setLastAfter(afterData); }, [afterData]); if (afterError) return afterError; // if the query returns less that we ask, then we have reach the end or beginning const isReachingEnd = afterData && afterData.data.publicAccounts.length < PAGE_SIZE; const isReachingStart = false; const pagination = { isReachingEnd, isReachingStart, loadMore: () => { if (!afterData || isReachingEnd) return; if (afterData.data.publicAccounts.length < MAX_RESULT_SIZE) { setPage(page + 1); } }, loadMorePrev: () => { null }, }; const publicAccounts = !afterData ? [] : (afterData || lastAfter).data.publicAccounts; if (loadingAfter) return { loading: true, data: { publicAccounts } }; if (afterData) { return { ok: true, data: { publicAccounts }, ...pagination }; } return { loading: true }; } /** * FIXME: mutate result when balance change (transaction ) * @param account * @param args * @returns */ export function useTransactions( account: string, args?: PaginationFilter, ): HttpResponsePaginated { const { paginatedFetcher } = useAuthenticatedBackend(); const [page, setPage] = useState(1); const { data: afterData, error: afterError, isValidating: loadingAfter, } = useSWR< HttpResponseOk, HttpError >([`access-api/accounts/${account}/transactions`, args?.page, PAGE_SIZE], paginatedFetcher); const [lastAfter, setLastAfter] = useState< HttpResponse >({ loading: true }); useEffect(() => { if (afterData) setLastAfter(afterData); }, [afterData]); if (afterError) return afterError; // if the query returns less that we ask, then we have reach the end or beginning const isReachingEnd = afterData && afterData.data.transactions.length < PAGE_SIZE; const isReachingStart = false; const pagination = { isReachingEnd, isReachingStart, loadMore: () => { if (!afterData || isReachingEnd) return; if (afterData.data.transactions.length < MAX_RESULT_SIZE) { setPage(page + 1); } }, loadMorePrev: () => { null }, }; const transactions = !afterData ? [] : (afterData || lastAfter).data.transactions; if (loadingAfter) return { loading: true, data: { transactions } }; if (afterData) { return { ok: true, data: { transactions }, ...pagination }; } return { loading: true }; }