/* 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 { HttpError, HttpResponse, HttpResponseOk, HttpResponsePaginated, RequestError } from "@gnu-taler/web-util/lib/index.browser"; import { useEffect, useMemo, useState } from "preact/hooks"; import useSWR from "swr"; import { useBackendContext } from "../context/backend.js"; import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js"; import { useAuthenticatedBackend } from "./backend.js"; export function useAdminAccountAPI(): AdminAccountAPI { 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 createAccount = async ( data: SandboxBackend.Circuit.CircuitAccountRequest, ): Promise> => { const res = await request(`circuit-api/accounts`, { method: "POST", data, contentType: "json" }); return res; }; const updateAccount = async ( account: string, data: SandboxBackend.Circuit.CircuitAccountReconfiguration, ): Promise> => { const res = await request(`circuit-api/accounts/${account}`, { method: "PATCH", data, contentType: "json" }); return res; }; const deleteAccount = async ( account: string, ): Promise> => { const res = await request(`circuit-api/accounts/${account}`, { method: "DELETE", contentType: "json" }); return res; }; const changePassword = async ( account: string, data: SandboxBackend.Circuit.AccountPasswordChange, ): Promise> => { const res = await request(`circuit-api/accounts/${account}/auth`, { method: "PATCH", data, contentType: "json" }); return res; }; return { createAccount, deleteAccount, updateAccount, changePassword }; } export function useCircuitAccountAPI(): CircuitAccountAPI { 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 updateAccount = async ( data: SandboxBackend.Circuit.CircuitAccountReconfiguration, ): Promise> => { const res = await request(`circuit-api/accounts/${account}`, { method: "PATCH", data, contentType: "json" }); return res; }; const changePassword = async ( data: SandboxBackend.Circuit.AccountPasswordChange, ): Promise> => { const res = await request(`circuit-api/accounts/${account}/auth`, { method: "PATCH", data, contentType: "json" }); return res; }; return { updateAccount, changePassword }; } export interface AdminAccountAPI { createAccount: ( data: SandboxBackend.Circuit.CircuitAccountRequest, ) => Promise>; deleteAccount: (account: string) => Promise>; updateAccount: ( account: string, data: SandboxBackend.Circuit.CircuitAccountReconfiguration ) => Promise>; changePassword: ( account: string, data: SandboxBackend.Circuit.AccountPasswordChange ) => Promise>; } export interface CircuitAccountAPI { updateAccount: ( data: SandboxBackend.Circuit.CircuitAccountReconfiguration ) => Promise>; changePassword: ( data: SandboxBackend.Circuit.AccountPasswordChange ) => Promise>; } export interface InstanceTemplateFilter { //FIXME: add filter to the template list position?: string; } export function useMyAccountDetails(): HttpResponse { const { fetcher } = useAuthenticatedBackend(); const { state } = useBackendContext() if (state.status === "loggedOut") { throw Error("can't access my-account-details when logged out") } const { data, error } = useSWR< HttpResponseOk, HttpError >([`accounts/${state.username}`], 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 }; } export function useAccountDetails(account: string): HttpResponse { const { fetcher } = useAuthenticatedBackend(); const { data, error } = useSWR< HttpResponseOk, RequestError >([`circuit-api/accounts/${account}`], 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.info; return { loading: true }; } interface PaginationFilter { account?: string, page?: number, } export function useAccounts( args?: PaginationFilter, ): HttpResponsePaginated { const { sandboxAccountsFetcher } = useAuthenticatedBackend(); const [page, setPage] = useState(0); const { data: afterData, error: afterError, // isValidating: loadingAfter, } = useSWR< HttpResponseOk, RequestError >([`circuit-api/accounts`, args?.page, PAGE_SIZE, args?.account], sandboxAccountsFetcher, { refreshInterval: 0, refreshWhenHidden: false, revalidateOnFocus: false, revalidateOnReconnect: false, refreshWhenOffline: false, errorRetryCount: 0, errorRetryInterval: 1, shouldRetryOnError: false, keepPreviousData: true, }); // const [lastAfter, setLastAfter] = useState< // HttpResponse // >({ loading: true }); // useEffect(() => { // if (afterData) setLastAfter(afterData); // }, [afterData]); // if the query returns less that we ask, then we have reach the end or beginning const isReachingEnd = afterData && afterData.data?.customers?.length < PAGE_SIZE; const isReachingStart = false; const pagination = { isReachingEnd, isReachingStart, loadMore: () => { if (!afterData || isReachingEnd) return; if (afterData.data?.customers?.length < MAX_RESULT_SIZE) { setPage(page + 1); } }, loadMorePrev: () => { null }, }; const result = useMemo(() => { const customers = !afterData ? [] : (afterData)?.data?.customers ?? []; return { ok: true as const, data: { customers }, ...pagination } }, [afterData?.data]) if (afterError) return afterError.info; if (afterData) { return result } // if (loadingAfter) // return { loading: true, data: { customers } }; // if (afterData) { // return { ok: true, data: { customers }, ...pagination }; // } return { loading: true }; } export function useCashouts(): HttpResponse< (SandboxBackend.Circuit.CashoutStatusResponse & WithId)[], SandboxBackend.SandboxError > { const { fetcher, multiFetcher } = useAuthenticatedBackend(); const { data: list, error: listError } = useSWR< HttpResponseOk, RequestError >([`circuit-api/cashouts`], fetcher, { refreshInterval: 0, refreshWhenHidden: false, revalidateOnFocus: false, revalidateOnReconnect: false, refreshWhenOffline: false, }); const paths = (list?.data.cashouts || []).map( (cashoutId) => `circuit-api/cashouts/${cashoutId}`, ); const { data: cashouts, error: productError } = useSWR< HttpResponseOk[], RequestError >([paths], multiFetcher, { refreshInterval: 0, refreshWhenHidden: false, revalidateOnFocus: false, revalidateOnReconnect: false, refreshWhenOffline: false, }); if (listError) return listError.info; if (productError) return productError.info; if (cashouts) { const dataWithId = cashouts.map((d) => { //take the id from the queried url return { ...d.data, id: d.info?.url.replace(/.*\/cashouts\//, "") || "", }; }); return { ok: true, data: dataWithId }; } return { loading: true }; }