diff options
Diffstat (limited to 'packages/merchant-backoffice-ui/src/hooks/backend.ts')
-rw-r--r-- | packages/merchant-backoffice-ui/src/hooks/backend.ts | 461 |
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, + }; +} |