aboutsummaryrefslogtreecommitdiff
path: root/packages/merchant-backoffice-ui/src/hooks/backend.ts
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2023-01-03 01:57:39 -0300
committerSebastian <sebasjm@gmail.com>2023-01-03 01:58:18 -0300
commita2668c22f0d18386fc988f27299172145d9fa15d (patch)
tree38f06046ce4d71ee3af64ede931754bfae6dc954 /packages/merchant-backoffice-ui/src/hooks/backend.ts
parentd1aa79eae817b1cf4c23f800308ecad101692ac7 (diff)
refactor better QA
removed axios, use fetch removed jest, added mocha and chai moved the default request handler to runtime dependency (so it can be replaced for testing) refactored ALL the test to the standard web-utils all hooks now use ONE request handler moved the tests from test folder to src
Diffstat (limited to 'packages/merchant-backoffice-ui/src/hooks/backend.ts')
-rw-r--r--packages/merchant-backoffice-ui/src/hooks/backend.ts461
1 files changed, 213 insertions, 248 deletions
diff --git a/packages/merchant-backoffice-ui/src/hooks/backend.ts b/packages/merchant-backoffice-ui/src/hooks/backend.ts
index cbfac35de..a0639a4a0 100644
--- a/packages/merchant-backoffice-ui/src/hooks/backend.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/backend.ts
@@ -20,15 +20,16 @@
*/
import { useSWRConfig } from "swr";
-import axios, { AxiosError, AxiosResponse } from "axios";
import { MerchantBackend } from "../declaration.js";
import { useBackendContext } from "../context/backend.js";
-import { useEffect, useState } from "preact/hooks";
-import { DEFAULT_REQUEST_TIMEOUT } from "../utils/constants.js";
+import { useCallback, useEffect, useState } from "preact/hooks";
+import { useInstanceContext } from "../context/instance.js";
import {
- axiosHandler,
- removeAxiosCancelToken,
-} from "../utils/switchableAxios.js";
+ HttpResponse,
+ HttpResponseOk,
+ RequestOptions,
+} from "../utils/request.js";
+import { useApiContext } from "../context/api.js";
export function useMatchMutate(): (
re: RegExp,
@@ -44,9 +45,7 @@ export function useMatchMutate(): (
return function matchRegexMutate(re: RegExp, value?: unknown) {
const allKeys = Array.from(cache.keys());
- // console.log(allKeys)
const keys = allKeys.filter((key) => re.test(key));
- // console.log(allKeys.length, keys.length)
const mutations = keys.map((key) => {
// console.log(key)
mutate(key, value, true);
@@ -55,268 +54,234 @@ export function useMatchMutate(): (
};
}
-export type HttpResponse<T> =
- | HttpResponseOk<T>
- | HttpResponseLoading<T>
- | HttpError;
-export type HttpResponsePaginated<T> =
- | HttpResponseOkPaginated<T>
- | HttpResponseLoading<T>
- | HttpError;
-
-export interface RequestInfo {
- url: string;
- hasToken: boolean;
- params: unknown;
- data: unknown;
- status: number;
-}
-
-interface HttpResponseLoading<T> {
- ok?: false;
- loading: true;
- clientError?: false;
- serverError?: false;
-
- data?: T;
-}
-export interface HttpResponseOk<T> {
- ok: true;
- loading?: false;
- clientError?: false;
- serverError?: false;
-
- data: T;
- info?: RequestInfo;
-}
-
-export type HttpResponseOkPaginated<T> = HttpResponseOk<T> & WithPagination;
-
-export interface WithPagination {
- loadMore: () => void;
- loadMorePrev: () => void;
- isReachingEnd?: boolean;
- isReachingStart?: boolean;
-}
-
-export type HttpError =
- | HttpResponseClientError
- | HttpResponseServerError
- | HttpResponseUnexpectedError;
-export interface SwrError {
- info: unknown;
- status: number;
- message: string;
-}
-export interface HttpResponseServerError {
- ok?: false;
- loading?: false;
- clientError?: false;
- serverError: true;
-
- error?: MerchantBackend.ErrorDetail;
- status: number;
- message: string;
- info?: RequestInfo;
-}
-interface HttpResponseClientError {
- ok?: false;
- loading?: false;
- clientError: true;
- serverError?: false;
-
- info?: RequestInfo;
- isUnauthorized: boolean;
- isNotfound: boolean;
- status: number;
- error?: MerchantBackend.ErrorDetail;
- message: string;
-}
-
-interface HttpResponseUnexpectedError {
- ok?: false;
- loading?: false;
- clientError?: false;
- serverError?: false;
-
- info?: RequestInfo;
- status?: number;
- error: unknown;
- message: string;
-}
-
-type Methods = "get" | "post" | "patch" | "delete" | "put";
-
-interface RequestOptions {
- method?: Methods;
- token?: string;
- data?: unknown;
- params?: unknown;
-}
-
-function buildRequestOk<T>(
- res: AxiosResponse<T>,
- url: string,
- hasToken: boolean,
-): HttpResponseOk<T> {
- return {
- ok: true,
- data: res.data,
- info: {
- params: res.config.params,
- data: res.config.data,
- url,
- hasToken,
- status: res.status,
- },
- };
-}
-
-// function buildResponse<T>(data?: T, error?: MerchantBackend.ErrorDetail, isValidating?: boolean): HttpResponse<T> {
-// if (isValidating) return {loading: true}
-// if (error) return buildRequestFailed()
-// }
-
-function buildRequestFailed(
- ex: AxiosError<MerchantBackend.ErrorDetail>,
- url: string,
- hasToken: boolean,
-):
- | HttpResponseClientError
- | HttpResponseServerError
- | HttpResponseUnexpectedError {
- const status = ex.response?.status;
-
- const info: RequestInfo = {
- data: ex.request?.data,
- params: ex.request?.params,
- url,
- hasToken,
- status: status || 0,
- };
-
- if (status && status >= 400 && status < 500) {
- const error: HttpResponseClientError = {
- clientError: true,
- isNotfound: status === 404,
- isUnauthorized: status === 401,
- status,
- info,
- message: ex.response?.data?.hint || ex.message,
- error: ex.response?.data,
- };
- return error;
- }
- if (status && status >= 500 && status < 600) {
- const error: HttpResponseServerError = {
- serverError: true,
- status,
- info,
- message:
- `${ex.response?.data?.hint} (code ${ex.response?.data?.code})` ||
- ex.message,
- error: ex.response?.data,
- };
- return error;
- }
-
- const error: HttpResponseUnexpectedError = {
- info,
- status,
- error: ex,
- message: ex.message,
- };
-
- return error;
-}
-
-const CancelToken = axios.CancelToken;
-let source = CancelToken.source();
-
-export function cancelPendingRequest(): void {
- source.cancel("canceled by the user");
- source = CancelToken.source();
-}
-
-export function isAxiosError<T>(
- error: AxiosError | any,
-): error is AxiosError<T> {
- return error && error.isAxiosError;
-}
-
-export async function request<T>(
- url: string,
- options: RequestOptions = {},
-): Promise<HttpResponseOk<T>> {
- const headers = options.token
- ? { Authorization: `Bearer ${options.token}` }
- : undefined;
-
- try {
- const res = await axiosHandler({
- url,
- responseType: "json",
- headers,
- cancelToken: !removeAxiosCancelToken ? source.token : undefined,
- method: options.method || "get",
- data: options.data,
- params: options.params,
- timeout: DEFAULT_REQUEST_TIMEOUT * 1000,
- });
- return buildRequestOk<T>(res, url, !!options.token);
- } catch (e) {
- if (isAxiosError<MerchantBackend.ErrorDetail>(e)) {
- const error = buildRequestFailed(e, url, !!options.token);
- throw error;
- }
- throw e;
- }
-}
-
-export function multiFetcher<T>(
- urls: string[],
- token: string,
- backend: string,
-): Promise<HttpResponseOk<T>[]> {
- return Promise.all(urls.map((url) => fetcher<T>(url, token, backend)));
-}
-
-export function fetcher<T>(
- url: string,
- token: string,
- backend: string,
-): Promise<HttpResponseOk<T>> {
- return request<T>(`${backend}${url}`, { token });
-}
-
export function useBackendInstancesTestForAdmin(): HttpResponse<MerchantBackend.Instances.InstancesResponse> {
- const { url, token } = useBackendContext();
+ const { request } = useBackendBaseRequest();
type Type = MerchantBackend.Instances.InstancesResponse;
const [result, setResult] = useState<HttpResponse<Type>>({ loading: true });
useEffect(() => {
- request<Type>(`${url}/management/instances`, { token })
+ request<Type>(`/management/instances`)
.then((data) => setResult(data))
.catch((error) => setResult(error));
- }, [url, token]);
+ }, [request]);
return result;
}
export function useBackendConfig(): HttpResponse<MerchantBackend.VersionResponse> {
- const { url, token } = useBackendContext();
+ const { request } = useBackendBaseRequest();
type Type = MerchantBackend.VersionResponse;
const [result, setResult] = useState<HttpResponse<Type>>({ loading: true });
useEffect(() => {
- request<Type>(`${url}/config`, { token })
+ request<Type>(`/config`)
.then((data) => setResult(data))
.catch((error) => setResult(error));
- }, [url, token]);
+ }, [request]);
return result;
}
+
+interface useBackendInstanceRequestType {
+ request: <T>(
+ path: string,
+ options?: RequestOptions,
+ ) => Promise<HttpResponseOk<T>>;
+ fetcher: <T>(path: string) => Promise<HttpResponseOk<T>>;
+ reserveDetailFetcher: <T>(path: string) => Promise<HttpResponseOk<T>>;
+ tipsDetailFetcher: <T>(path: string) => Promise<HttpResponseOk<T>>;
+ multiFetcher: <T>(url: string[]) => Promise<HttpResponseOk<T>[]>;
+ orderFetcher: <T>(
+ path: string,
+ paid?: YesOrNo,
+ refunded?: YesOrNo,
+ wired?: YesOrNo,
+ searchDate?: Date,
+ delta?: number,
+ ) => Promise<HttpResponseOk<T>>;
+ transferFetcher: <T>(
+ path: string,
+ payto_uri?: string,
+ verified?: string,
+ position?: string,
+ delta?: number,
+ ) => Promise<HttpResponseOk<T>>;
+ templateFetcher: <T>(
+ path: string,
+ position?: string,
+ delta?: number,
+ ) => Promise<HttpResponseOk<T>>;
+}
+interface useBackendBaseRequestType {
+ request: <T>(
+ path: string,
+ options?: RequestOptions,
+ ) => Promise<HttpResponseOk<T>>;
+}
+
+type YesOrNo = "yes" | "no";
+
+/**
+ *
+ * @param root the request is intended to the base URL and no the instance URL
+ * @returns request handler to
+ */
+export function useBackendBaseRequest(): useBackendBaseRequestType {
+ const { url: backend, token } = useBackendContext();
+ const { request: requestHandler } = useApiContext();
+
+ const request = useCallback(
+ function requestImpl<T>(
+ path: string,
+ options: RequestOptions = {},
+ ): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(backend, path, { token, ...options });
+ },
+ [backend, token],
+ );
+
+ return { request };
+}
+
+export function useBackendInstanceRequest(): useBackendInstanceRequestType {
+ const { url: baseUrl, token: baseToken } = useBackendContext();
+ const { token: instanceToken, id, admin } = useInstanceContext();
+ const { request: requestHandler } = useApiContext();
+
+ const { backend, token } = !admin
+ ? { backend: baseUrl, token: baseToken }
+ : { backend: `${baseUrl}/instances/${id}`, token: instanceToken };
+
+ const request = useCallback(
+ function requestImpl<T>(
+ path: string,
+ options: RequestOptions = {},
+ ): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(backend, path, { token, ...options });
+ },
+ [backend, token],
+ );
+
+ const multiFetcher = useCallback(
+ function multiFetcherImpl<T>(
+ paths: string[],
+ ): Promise<HttpResponseOk<T>[]> {
+ return Promise.all(
+ paths.map((path) => requestHandler<T>(backend, path, { token })),
+ );
+ },
+ [backend, token],
+ );
+
+ const fetcher = useCallback(
+ function fetcherImpl<T>(path: string): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(backend, path, { token });
+ },
+ [backend, token],
+ );
+
+ const orderFetcher = useCallback(
+ function orderFetcherImpl<T>(
+ path: string,
+ paid?: YesOrNo,
+ refunded?: YesOrNo,
+ wired?: YesOrNo,
+ searchDate?: Date,
+ delta?: number,
+ ): Promise<HttpResponseOk<T>> {
+ const date_ms =
+ delta && delta < 0 && searchDate
+ ? searchDate.getTime() + 1
+ : searchDate?.getTime();
+ const params: any = {};
+ if (paid !== undefined) params.paid = paid;
+ if (delta !== undefined) params.delta = delta;
+ if (refunded !== undefined) params.refunded = refunded;
+ if (wired !== undefined) params.wired = wired;
+ if (date_ms !== undefined) params.date_ms = date_ms;
+ return requestHandler<T>(backend, path, { params, token });
+ },
+ [backend, token],
+ );
+
+ const reserveDetailFetcher = useCallback(
+ function reserveDetailFetcherImpl<T>(
+ path: string,
+ ): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(backend, path, {
+ params: {
+ tips: "yes",
+ },
+ token,
+ });
+ },
+ [backend, token],
+ );
+
+ const tipsDetailFetcher = useCallback(
+ function tipsDetailFetcherImpl<T>(
+ path: string,
+ ): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(backend, path, {
+ params: {
+ pickups: "yes",
+ },
+ token,
+ });
+ },
+ [backend, token],
+ );
+
+ const transferFetcher = useCallback(
+ function transferFetcherImpl<T>(
+ path: string,
+ payto_uri?: string,
+ verified?: string,
+ position?: string,
+ delta?: number,
+ ): Promise<HttpResponseOk<T>> {
+ const params: any = {};
+ if (payto_uri !== undefined) params.payto_uri = payto_uri;
+ if (verified !== undefined) params.verified = verified;
+ if (delta !== undefined) {
+ params.limit = delta;
+ }
+ if (position !== undefined) params.offset = position;
+
+ return requestHandler<T>(backend, path, { params, token });
+ },
+ [backend, token],
+ );
+
+ const templateFetcher = useCallback(
+ function templateFetcherImpl<T>(
+ path: string,
+ position?: string,
+ delta?: number,
+ ): Promise<HttpResponseOk<T>> {
+ const params: any = {};
+ if (delta !== undefined) {
+ params.limit = delta;
+ }
+ if (position !== undefined) params.offset = position;
+
+ return requestHandler<T>(backend, path, { params, token });
+ },
+ [backend, token],
+ );
+
+ return {
+ request,
+ fetcher,
+ multiFetcher,
+ orderFetcher,
+ reserveDetailFetcher,
+ tipsDetailFetcher,
+ transferFetcher,
+ templateFetcher,
+ };
+}