wallet-core/packages/demobank-ui/src/hooks/access.ts
2023-02-08 17:41:19 -03:00

331 lines
10 KiB
TypeScript

/*
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 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<HttpResponseOk<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>> => {
const res = await request<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>(`access-api/accounts/${account}/withdrawals`, {
method: "POST",
data,
contentType: "json"
});
return res;
};
const abortWithdrawal = async (
id: string,
): Promise<HttpResponseOk<void>> => {
const res = await request<void>(`access-api/accounts/${account}/withdrawals/${id}`, {
method: "POST",
contentType: "json"
});
await mutateAll(/.*accounts\/.*\/withdrawals\/.*/);
return res;
};
const confirmWithdrawal = async (
id: string,
): Promise<HttpResponseOk<void>> => {
const res = await request<void>(`access-api/accounts/${account}/withdrawals/${id}`, {
method: "POST",
contentType: "json"
});
await mutateAll(/.*accounts\/.*\/withdrawals\/.*/);
return res;
};
const createTransaction = async (
data: SandboxBackend.Access.CreateBankAccountTransactionCreate
): Promise<HttpResponseOk<void>> => {
const res = await request<void>(`access-api/accounts/${account}/transactions`, {
method: "POST",
data,
contentType: "json"
});
await mutateAll(/.*accounts\/.*\/transactions.*/);
return res;
};
const deleteAccount = async (
): Promise<HttpResponseOk<void>> => {
const res = await request<void>(`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<HttpResponseOk<void>> => {
const res = await noAuthRequest<void>(`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<HttpResponseOk<void>>;
}
export interface AccessAPI {
createWithdrawal: (
data: SandboxBackend.Access.BankAccountCreateWithdrawalRequest,
) => Promise<HttpResponseOk<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>>;
abortWithdrawal: (
wid: string,
) => Promise<HttpResponseOk<void>>;
confirmWithdrawal: (
wid: string
) => Promise<HttpResponseOk<void>>;
createTransaction: (
data: SandboxBackend.Access.CreateBankAccountTransactionCreate
) => Promise<HttpResponseOk<void>>;
deleteAccount: () => Promise<HttpResponseOk<void>>;
}
export interface InstanceTemplateFilter {
//FIXME: add filter to the template list
position?: string;
}
export function useAccountDetails(account: string): HttpResponse<SandboxBackend.Access.BankAccountBalanceResponse, SandboxBackend.SandboxError> {
const { fetcher } = useAuthenticatedBackend();
const { data, error } = useSWR<
HttpResponseOk<SandboxBackend.Access.BankAccountBalanceResponse>,
HttpError<SandboxBackend.SandboxError>
>([`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<SandboxBackend.Access.BankAccountGetWithdrawalResponse, SandboxBackend.SandboxError> {
const { fetcher } = useAuthenticatedBackend();
const { data, error } = useSWR<
HttpResponseOk<SandboxBackend.Access.BankAccountGetWithdrawalResponse>,
HttpError<SandboxBackend.SandboxError>
>([`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<SandboxBackend.Access.BankAccountTransactionInfo, SandboxBackend.SandboxError> {
const { fetcher } = useAuthenticatedBackend();
const { data, error } = useSWR<
HttpResponseOk<SandboxBackend.Access.BankAccountTransactionInfo>,
HttpError<SandboxBackend.SandboxError>
>([`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<SandboxBackend.Access.PublicAccountsResponse, SandboxBackend.SandboxError> {
const { paginatedFetcher } = usePublicBackend();
const [page, setPage] = useState(1);
const {
data: afterData,
error: afterError,
isValidating: loadingAfter,
} = useSWR<
HttpResponseOk<SandboxBackend.Access.PublicAccountsResponse>,
HttpError<SandboxBackend.SandboxError>
>([`public-accounts`, args?.page, PAGE_SIZE], paginatedFetcher);
const [lastAfter, setLastAfter] = useState<
HttpResponse<SandboxBackend.Access.PublicAccountsResponse, SandboxBackend.SandboxError>
>({ 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<SandboxBackend.Access.BankAccountTransactionsResponse, SandboxBackend.SandboxError> {
const { paginatedFetcher } = useAuthenticatedBackend();
const [page, setPage] = useState(1);
const {
data: afterData,
error: afterError,
isValidating: loadingAfter,
} = useSWR<
HttpResponseOk<SandboxBackend.Access.BankAccountTransactionsResponse>,
HttpError<SandboxBackend.SandboxError>
>([`access-api/accounts/${account}/transactions`, args?.page, PAGE_SIZE], paginatedFetcher);
const [lastAfter, setLastAfter] = useState<
HttpResponse<SandboxBackend.Access.BankAccountTransactionsResponse, SandboxBackend.SandboxError>
>({ 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 };
}