From a8c5a9696c1735a178158cbc9ac4f9bb4b6f013d Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 8 Feb 2023 17:41:19 -0300 Subject: impl accout management and refactor --- packages/demobank-ui/src/hooks/access.ts | 330 +++++++++++++++++++++++++++++++ 1 file changed, 330 insertions(+) create mode 100644 packages/demobank-ui/src/hooks/access.ts (limited to 'packages/demobank-ui/src/hooks/access.ts') diff --git a/packages/demobank-ui/src/hooks/access.ts b/packages/demobank-ui/src/hooks/access.ts new file mode 100644 index 000000000..4d4574dac --- /dev/null +++ b/packages/demobank-ui/src/hooks/access.ts @@ -0,0 +1,330 @@ +/* + 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 }; +} -- cgit v1.2.3