aboutsummaryrefslogtreecommitdiff
path: root/packages/demobank-ui/src/hooks
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2023-02-08 17:41:19 -0300
committerSebastian <sebasjm@gmail.com>2023-02-08 17:41:19 -0300
commita8c5a9696c1735a178158cbc9ac4f9bb4b6f013d (patch)
treefc24dbf06b548925dbc065a49060473fdd220c94 /packages/demobank-ui/src/hooks
parent9b0d887a1bc292f652352c1dba4ed4243a88bbbe (diff)
downloadwallet-core-a8c5a9696c1735a178158cbc9ac4f9bb4b6f013d.tar.xz
impl accout management and refactor
Diffstat (limited to 'packages/demobank-ui/src/hooks')
-rw-r--r--packages/demobank-ui/src/hooks/access.ts330
-rw-r--r--packages/demobank-ui/src/hooks/async.ts1
-rw-r--r--packages/demobank-ui/src/hooks/backend.ts195
-rw-r--r--packages/demobank-ui/src/hooks/circuit.ts317
4 files changed, 831 insertions, 12 deletions
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 <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 };
+}
diff --git a/packages/demobank-ui/src/hooks/async.ts b/packages/demobank-ui/src/hooks/async.ts
index 6492b7729..b968cfb84 100644
--- a/packages/demobank-ui/src/hooks/async.ts
+++ b/packages/demobank-ui/src/hooks/async.ts
@@ -62,7 +62,6 @@ export function useAsync<T>(
};
function cancel() {
- // cancelPendingRequest()
setLoading(false);
setSlow(false);
}
diff --git a/packages/demobank-ui/src/hooks/backend.ts b/packages/demobank-ui/src/hooks/backend.ts
index 13a158f4f..f4f5ecfd0 100644
--- a/packages/demobank-ui/src/hooks/backend.ts
+++ b/packages/demobank-ui/src/hooks/backend.ts
@@ -14,7 +14,17 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
+import { canonicalizeBaseUrl } from "@gnu-taler/taler-util";
import { useLocalStorage } from "@gnu-taler/web-util/lib/index.browser";
+import {
+ HttpResponse,
+ HttpResponseOk,
+ RequestOptions,
+} from "@gnu-taler/web-util/lib/index.browser";
+import { useApiContext } from "@gnu-taler/web-util/lib/index.browser";
+import { useCallback, useEffect, useState } from "preact/hooks";
+import { useSWRConfig } from "swr";
+import { useBackendContext } from "../context/backend.js";
/**
* Has the information to reach and
@@ -22,25 +32,38 @@ import { useLocalStorage } from "@gnu-taler/web-util/lib/index.browser";
*/
export type BackendState = LoggedIn | LoggedOut;
-export interface BackendInfo {
- url: string;
+export interface BackendCredentials {
username: string;
password: string;
}
-interface LoggedIn extends BackendInfo {
+interface LoggedIn extends BackendCredentials {
+ url: string;
status: "loggedIn";
+ isUserAdministrator: boolean;
}
interface LoggedOut {
+ url: string;
status: "loggedOut";
}
-export const defaultState: BackendState = { status: "loggedOut" };
+const maybeRootPath = "https://bank.demo.taler.net/demobanks/default/";
+
+export function getInitialBackendBaseURL(): string {
+ const overrideUrl = localStorage.getItem("bank-base-url");
+
+ return canonicalizeBaseUrl(overrideUrl ? overrideUrl : maybeRootPath);
+}
+
+export const defaultState: BackendState = {
+ status: "loggedOut",
+ url: getInitialBackendBaseURL()
+};
export interface BackendStateHandler {
state: BackendState;
- clear(): void;
- save(info: BackendInfo): void;
+ logOut(): void;
+ logIn(info: BackendCredentials): void;
}
/**
* Return getters and setters for
@@ -52,7 +75,7 @@ export function useBackendState(): BackendStateHandler {
"backend-state",
JSON.stringify(defaultState),
);
- // const parsed = value !== undefined ? JSON.parse(value) : value;
+
let parsed;
try {
parsed = JSON.parse(value!);
@@ -63,12 +86,162 @@ export function useBackendState(): BackendStateHandler {
return {
state,
- clear() {
- update(JSON.stringify(defaultState));
+ logOut() {
+ update(JSON.stringify({ ...defaultState, url: state.url }));
},
- save(info) {
- const nextState: BackendState = { status: "loggedIn", ...info };
+ logIn(info) {
+ //admin is defined by the username
+ const nextState: BackendState = { status: "loggedIn", url: state.url, ...info, isUserAdministrator: info.username === "admin" };
update(JSON.stringify(nextState));
},
};
}
+
+interface useBackendType {
+ request: <T>(
+ path: string,
+ options?: RequestOptions,
+ ) => Promise<HttpResponseOk<T>>;
+ fetcher: <T>(endpoint: string) => Promise<HttpResponseOk<T>>;
+ multiFetcher: <T>(endpoint: string[]) => Promise<HttpResponseOk<T>[]>;
+ paginatedFetcher: <T>(args: [string, number, number]) => Promise<HttpResponseOk<T>>;
+ sandboxAccountsFetcher: <T>(args: [string, number, number, string]) => Promise<HttpResponseOk<T>>;
+}
+
+
+export function usePublicBackend(): useBackendType {
+ const { state } = useBackendContext();
+ const { request: requestHandler } = useApiContext();
+
+ const baseUrl = state.url
+
+ const request = useCallback(
+ function requestImpl<T>(
+ path: string,
+ options: RequestOptions = {},
+ ): Promise<HttpResponseOk<T>> {
+
+ return requestHandler<T>(baseUrl, path, options);
+ },
+ [baseUrl],
+ );
+
+ const fetcher = useCallback(
+ function fetcherImpl<T>(endpoint: string): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(baseUrl, endpoint);
+ },
+ [baseUrl],
+ );
+ const paginatedFetcher = useCallback(
+ function fetcherImpl<T>([endpoint, page, size]: [string, number, number]): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(baseUrl, endpoint, { params: { page: page || 1, size } });
+ },
+ [baseUrl],
+ );
+ const multiFetcher = useCallback(
+ function multiFetcherImpl<T>(
+ endpoints: string[],
+ ): Promise<HttpResponseOk<T>[]> {
+ return Promise.all(
+ endpoints.map((endpoint) => requestHandler<T>(baseUrl, endpoint)),
+ );
+ },
+ [baseUrl],
+ );
+ const sandboxAccountsFetcher = useCallback(
+ function fetcherImpl<T>([endpoint, page, size, account]: [string, number, number, string]): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(baseUrl, endpoint, { params: { page: page || 1, size } });
+ },
+ [baseUrl],
+ );
+ return { request, fetcher, paginatedFetcher, multiFetcher, sandboxAccountsFetcher };
+}
+
+export function useAuthenticatedBackend(): useBackendType {
+ const { state } = useBackendContext();
+ const { request: requestHandler } = useApiContext();
+
+ const creds = state.status === "loggedIn" ? state : undefined
+ const baseUrl = state.url
+
+ const request = useCallback(
+ function requestImpl<T>(
+ path: string,
+ options: RequestOptions = {},
+ ): Promise<HttpResponseOk<T>> {
+
+ return requestHandler<T>(baseUrl, path, { basicAuth: creds, ...options });
+ },
+ [baseUrl, creds],
+ );
+
+ const fetcher = useCallback(
+ function fetcherImpl<T>(endpoint: string): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(baseUrl, endpoint, { basicAuth: creds });
+ },
+ [baseUrl, creds],
+ );
+ const paginatedFetcher = useCallback(
+ function fetcherImpl<T>([endpoint, page = 0, size]: [string, number, number]): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(baseUrl, endpoint, { basicAuth: creds, params: { page, size } });
+ },
+ [baseUrl, creds],
+ );
+ const multiFetcher = useCallback(
+ function multiFetcherImpl<T>(
+ endpoints: string[],
+ ): Promise<HttpResponseOk<T>[]> {
+ return Promise.all(
+ endpoints.map((endpoint) => requestHandler<T>(baseUrl, endpoint, { basicAuth: creds })),
+ );
+ },
+ [baseUrl, creds],
+ );
+ const sandboxAccountsFetcher = useCallback(
+ function fetcherImpl<T>([endpoint, page, size, account]: [string, number, number, string]): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(baseUrl, endpoint, { basicAuth: creds, params: { page: page || 1, size } });
+ },
+ [baseUrl],
+ );
+ return { request, fetcher, paginatedFetcher, multiFetcher, sandboxAccountsFetcher };
+}
+
+export function useBackendConfig(): HttpResponse<SandboxBackend.Config, SandboxBackend.SandboxError> {
+ const { request } = usePublicBackend();
+
+ type Type = SandboxBackend.Config;
+
+ const [result, setResult] = useState<HttpResponse<Type, SandboxBackend.SandboxError>>({ loading: true });
+
+ useEffect(() => {
+ request<Type>(`/config`)
+ .then((data) => setResult(data))
+ .catch((error) => setResult(error));
+ }, [request]);
+
+ return result;
+}
+
+export function useMatchMutate(): (
+ re: RegExp,
+ value?: unknown,
+) => Promise<any> {
+ const { cache, mutate } = useSWRConfig();
+
+ if (!(cache instanceof Map)) {
+ throw new Error(
+ "matchMutate requires the cache provider to be a Map instance",
+ );
+ }
+
+ return function matchRegexMutate(re: RegExp, value?: unknown) {
+ const allKeys = Array.from(cache.keys());
+ const keys = allKeys.filter((key) => re.test(key));
+ const mutations = keys.map((key) => {
+ mutate(key, value, true);
+ });
+ return Promise.all(mutations);
+ };
+}
+
+
diff --git a/packages/demobank-ui/src/hooks/circuit.ts b/packages/demobank-ui/src/hooks/circuit.ts
new file mode 100644
index 000000000..6e9ada601
--- /dev/null
+++ b/packages/demobank-ui/src/hooks/circuit.ts
@@ -0,0 +1,317 @@
+/*
+ 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 {
+ 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<HttpResponseOk<void>> => {
+ const res = await request<void>(`circuit-api/accounts`, {
+ method: "POST",
+ data,
+ contentType: "json"
+ });
+ return res;
+ };
+
+ const updateAccount = async (
+ account: string,
+ data: SandboxBackend.Circuit.CircuitAccountReconfiguration,
+ ): Promise<HttpResponseOk<void>> => {
+ const res = await request<void>(`circuit-api/accounts/${account}`, {
+ method: "PATCH",
+ data,
+ contentType: "json"
+ });
+ return res;
+ };
+ const deleteAccount = async (
+ account: string,
+ ): Promise<HttpResponseOk<void>> => {
+ const res = await request<void>(`circuit-api/accounts/${account}`, {
+ method: "DELETE",
+ contentType: "json"
+ });
+ return res;
+ };
+ const changePassword = async (
+ account: string,
+ data: SandboxBackend.Circuit.AccountPasswordChange,
+ ): Promise<HttpResponseOk<void>> => {
+ const res = await request<void>(`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<HttpResponseOk<void>> => {
+ const res = await request<void>(`circuit-api/accounts/${account}`, {
+ method: "PATCH",
+ data,
+ contentType: "json"
+ });
+ return res;
+ };
+ const changePassword = async (
+ data: SandboxBackend.Circuit.AccountPasswordChange,
+ ): Promise<HttpResponseOk<void>> => {
+ const res = await request<void>(`circuit-api/accounts/${account}/auth`, {
+ method: "PATCH",
+ data,
+ contentType: "json"
+ });
+ return res;
+ };
+
+ return { updateAccount, changePassword };
+}
+
+export interface AdminAccountAPI {
+ createAccount: (
+ data: SandboxBackend.Circuit.CircuitAccountRequest,
+ ) => Promise<HttpResponseOk<void>>;
+ deleteAccount: (account: string) => Promise<HttpResponseOk<void>>;
+
+ updateAccount: (
+ account: string,
+ data: SandboxBackend.Circuit.CircuitAccountReconfiguration
+ ) => Promise<HttpResponseOk<void>>;
+ changePassword: (
+ account: string,
+ data: SandboxBackend.Circuit.AccountPasswordChange
+ ) => Promise<HttpResponseOk<void>>;
+}
+
+export interface CircuitAccountAPI {
+ updateAccount: (
+ data: SandboxBackend.Circuit.CircuitAccountReconfiguration
+ ) => Promise<HttpResponseOk<void>>;
+ changePassword: (
+ data: SandboxBackend.Circuit.AccountPasswordChange
+ ) => Promise<HttpResponseOk<void>>;
+}
+
+
+export interface InstanceTemplateFilter {
+ //FIXME: add filter to the template list
+ position?: string;
+}
+
+
+export function useMyAccountDetails(): HttpResponse<SandboxBackend.Circuit.CircuitAccountData, SandboxBackend.SandboxError> {
+ 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<SandboxBackend.Circuit.CircuitAccountData>,
+ HttpError<SandboxBackend.SandboxError>
+ >([`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<SandboxBackend.Circuit.CircuitAccountData, SandboxBackend.SandboxError> {
+ const { fetcher } = useAuthenticatedBackend();
+
+ const { data, error } = useSWR<
+ HttpResponseOk<SandboxBackend.Circuit.CircuitAccountData>,
+ RequestError<SandboxBackend.SandboxError>
+ >([`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<SandboxBackend.Circuit.CircuitAccounts, SandboxBackend.SandboxError> {
+ const { sandboxAccountsFetcher } = useAuthenticatedBackend();
+ const [page, setPage] = useState(0);
+
+ const {
+ data: afterData,
+ error: afterError,
+ // isValidating: loadingAfter,
+ } = useSWR<
+ HttpResponseOk<SandboxBackend.Circuit.CircuitAccounts>,
+ RequestError<SandboxBackend.SandboxError>
+ >([`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<SandboxBackend.Circuit.CircuitAccounts, SandboxBackend.SandboxError>
+ // >({ 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<SandboxBackend.Circuit.Cashouts>,
+ RequestError<SandboxBackend.SandboxError>
+ >([`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<SandboxBackend.Circuit.CashoutStatusResponse>[],
+ RequestError<SandboxBackend.SandboxError>
+ >([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 };
+}