diff options
author | Sebastian <sebasjm@gmail.com> | 2023-03-02 13:51:03 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2023-03-02 13:51:03 -0300 |
commit | 2d5fbb22cdf6cde10004cea174fc90e71668746b (patch) | |
tree | 21be3694b459fa59f110d87346160dd048d2a280 /packages/merchant-backend-ui/src/hooks | |
parent | f446a5921487c7196904f9aab00fc189ac6a9e3b (diff) |
fix #7714
Diffstat (limited to 'packages/merchant-backend-ui/src/hooks')
-rw-r--r-- | packages/merchant-backend-ui/src/hooks/async.ts | 76 | ||||
-rw-r--r-- | packages/merchant-backend-ui/src/hooks/backend.ts | 264 | ||||
-rw-r--r-- | packages/merchant-backend-ui/src/hooks/index.ts | 110 | ||||
-rw-r--r-- | packages/merchant-backend-ui/src/hooks/instance.ts | 187 | ||||
-rw-r--r-- | packages/merchant-backend-ui/src/hooks/listener.ts | 68 | ||||
-rw-r--r-- | packages/merchant-backend-ui/src/hooks/notification.ts | 43 | ||||
-rw-r--r-- | packages/merchant-backend-ui/src/hooks/notifications.ts | 48 | ||||
-rw-r--r-- | packages/merchant-backend-ui/src/hooks/order.ts | 217 | ||||
-rw-r--r-- | packages/merchant-backend-ui/src/hooks/product.ts | 223 | ||||
-rw-r--r-- | packages/merchant-backend-ui/src/hooks/tips.ts | 159 | ||||
-rw-r--r-- | packages/merchant-backend-ui/src/hooks/transfer.ts | 150 |
11 files changed, 0 insertions, 1545 deletions
diff --git a/packages/merchant-backend-ui/src/hooks/async.ts b/packages/merchant-backend-ui/src/hooks/async.ts deleted file mode 100644 index fd550043b..000000000 --- a/packages/merchant-backend-ui/src/hooks/async.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ -import { useState } from "preact/hooks"; -import { cancelPendingRequest } from "./backend"; - -export interface Options { - slowTolerance: number, -} - -export interface AsyncOperationApi<T> { - request: (...a: any) => void, - cancel: () => void, - data: T | undefined, - isSlow: boolean, - isLoading: boolean, - error: string | undefined -} - -export function useAsync<T>(fn?: (...args: any) => Promise<T>, { slowTolerance: tooLong }: Options = { slowTolerance: 1000 }): AsyncOperationApi<T> { - const [data, setData] = useState<T | undefined>(undefined); - const [isLoading, setLoading] = useState<boolean>(false); - const [error, setError] = useState<any>(undefined); - const [isSlow, setSlow] = useState(false) - - const request = async (...args: any) => { - if (!fn) return; - setLoading(true); - - const handler = setTimeout(() => { - setSlow(true) - }, tooLong) - - try { - const result = await fn(...args); - setData(result); - } catch (error) { - setError(error); - } - setLoading(false); - setSlow(false) - clearTimeout(handler) - }; - - function cancel() { - cancelPendingRequest() - setLoading(false); - setSlow(false) - } - - return { - request, - cancel, - data, - isSlow, - isLoading, - error - }; -} diff --git a/packages/merchant-backend-ui/src/hooks/backend.ts b/packages/merchant-backend-ui/src/hooks/backend.ts deleted file mode 100644 index 044344d5d..000000000 --- a/packages/merchant-backend-ui/src/hooks/backend.ts +++ /dev/null @@ -1,264 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { mutate, cache } from 'swr'; -import axios, { AxiosError, AxiosResponse } from 'axios' -import { MerchantBackend } from '../declaration'; -import { useBackendContext } from '../context/backend'; -import { useEffect, useState } from 'preact/hooks'; -import { DEFAULT_REQUEST_TIMEOUT } from '../utils/constants'; - -export function mutateAll(re: RegExp, value?: unknown): Array<Promise<any>> { - return cache.keys().filter(key => { - return re.test(key) - }).map(key => { - return mutate(key, value) - }) -} - -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; -} - -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, - } - } -} - -// 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, - }; - - 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() { - source.cancel('canceled by the user') - source = CancelToken.source() -} - -let removeAxiosCancelToken = false -/** - * Jest mocking seems to break when using the cancelToken property. - * Using this workaround when testing while finding the correct solution - */ -export function setAxiosRequestAsTestingEnvironment() { - removeAxiosCancelToken = true -} - -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 axios({ - 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 (axios.isAxiosError(e)) { - throw buildRequestFailed(e, url, !!options.token) - } - throw e - } - -} - -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() - - type Type = MerchantBackend.Instances.InstancesResponse; - - const [result, setResult] = useState<HttpResponse<Type>>({ loading: true }) - - useEffect(() => { - request<Type>(`${url}/management/instances`, { token }) - .then(data => setResult(data)) - .catch(error => setResult(error)) - }, [url, token]) - - - return result -} - - -export function useBackendConfig(): HttpResponse<MerchantBackend.VersionResponse> { - const { url, token } = useBackendContext() - - type Type = MerchantBackend.VersionResponse; - - const [result, setResult] = useState<HttpResponse<Type>>({ loading: true }) - - useEffect(() => { - request<Type>(`${url}/config`, { token }) - .then(data => setResult(data)) - .catch(error => setResult(error)) - }, [url, token]) - - return result -} diff --git a/packages/merchant-backend-ui/src/hooks/index.ts b/packages/merchant-backend-ui/src/hooks/index.ts deleted file mode 100644 index 19d672ad3..000000000 --- a/packages/merchant-backend-ui/src/hooks/index.ts +++ /dev/null @@ -1,110 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { StateUpdater, useCallback, useState } from "preact/hooks"; -import { ValueOrFunction } from '../utils/types'; - - -const calculateRootPath = () => { - const rootPath = typeof window !== undefined ? window.location.origin + window.location.pathname : '/' - return rootPath -} - -export function useBackendURL(url?: string): [string, boolean, StateUpdater<string>, () => void] { - const [value, setter] = useNotNullLocalStorage('backend-url', url || calculateRootPath()) - const [triedToLog, setTriedToLog] = useLocalStorage('tried-login') - - const checkedSetter = (v: ValueOrFunction<string>) => { - setTriedToLog('yes') - return setter(p => (v instanceof Function ? v(p) : v).replace(/\/$/, '')) - } - - const resetBackend = () => { - setTriedToLog(undefined) - } - return [value, !!triedToLog, checkedSetter, resetBackend] -} - -export function useBackendDefaultToken(): [string | undefined, StateUpdater<string | undefined>] { - return useLocalStorage('backend-token') -} - -export function useBackendInstanceToken(id: string): [string | undefined, StateUpdater<string | undefined>] { - const [token, setToken] = useLocalStorage(`backend-token-${id}`) - const [defaultToken, defaultSetToken] = useBackendDefaultToken() - - // instance named 'default' use the default token - if (id === 'default') { - return [defaultToken, defaultSetToken] - } - - return [token, setToken] -} - -export function useLang(initial?: string): [string, StateUpdater<string>] { - const browserLang = typeof window !== "undefined" ? navigator.language || (navigator as any).userLanguage : undefined; - const defaultLang = (browserLang || initial || 'en').substring(0, 2) - return useNotNullLocalStorage('lang-preference', defaultLang) -} - -export function useLocalStorage(key: string, initialValue?: string): [string | undefined, StateUpdater<string | undefined>] { - const [storedValue, setStoredValue] = useState<string | undefined>((): string | undefined => { - return typeof window !== "undefined" ? window.localStorage.getItem(key) || initialValue : initialValue; - }); - - const setValue = (value?: string | ((val?: string) => string | undefined)) => { - setStoredValue(p => { - const toStore = value instanceof Function ? value(p) : value - if (typeof window !== "undefined") { - if (!toStore) { - window.localStorage.removeItem(key) - } else { - window.localStorage.setItem(key, toStore); - } - } - return toStore - }) - }; - - return [storedValue, setValue]; -} - -export function useNotNullLocalStorage(key: string, initialValue: string): [string, StateUpdater<string>] { - const [storedValue, setStoredValue] = useState<string>((): string => { - return typeof window !== "undefined" ? window.localStorage.getItem(key) || initialValue : initialValue; - }); - - const setValue = (value: string | ((val: string) => string)) => { - const valueToStore = value instanceof Function ? value(storedValue) : value; - setStoredValue(valueToStore); - if (typeof window !== "undefined") { - if (!valueToStore) { - window.localStorage.removeItem(key) - } else { - window.localStorage.setItem(key, valueToStore); - } - } - }; - - return [storedValue, setValue]; -} - - diff --git a/packages/merchant-backend-ui/src/hooks/instance.ts b/packages/merchant-backend-ui/src/hooks/instance.ts deleted file mode 100644 index 14ab8de9c..000000000 --- a/packages/merchant-backend-ui/src/hooks/instance.ts +++ /dev/null @@ -1,187 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 { MerchantBackend } from '../declaration'; -import { useBackendContext } from '../context/backend'; -import { fetcher, HttpError, HttpResponse, HttpResponseOk, request, SwrError } from './backend'; -import useSWR, { mutate } from 'swr'; -import { useInstanceContext } from '../context/instance'; - - -interface InstanceAPI { - updateInstance: (data: MerchantBackend.Instances.InstanceReconfigurationMessage) => Promise<void>; - deleteInstance: () => Promise<void>; - clearToken: () => Promise<void>; - setNewToken: (token: string) => Promise<void>; -} - -export function useManagementAPI(instanceId: string) : InstanceAPI { - const { url, token } = useBackendContext() - - const updateInstance = async (instance: MerchantBackend.Instances.InstanceReconfigurationMessage): Promise<void> => { - await request(`${url}/management/instances/${instanceId}`, { - method: 'patch', - token, - data: instance - }) - - mutate([`/private/`, token, url], null) - }; - - const deleteInstance = async (): Promise<void> => { - await request(`${url}/management/instances/${instanceId}`, { - method: 'delete', - token, - }) - - mutate([`/private/`, token, url], null) - } - - const clearToken = async (): Promise<void> => { - await request(`${url}/management/instances/${instanceId}/auth`, { - method: 'post', - token, - data: { method: 'external' } - }) - - mutate([`/private/`, token, url], null) - } - - const setNewToken = async (newToken: string): Promise<void> => { - await request(`${url}/management/instances/${instanceId}/auth`, { - method: 'post', - token, - data: { method: 'token', token: newToken } - }) - - mutate([`/private/`, token, url], null) - } - - return { updateInstance, deleteInstance, setNewToken, clearToken } -} - -export function useInstanceAPI(): InstanceAPI { - const { url: baseUrl, token: adminToken } = useBackendContext() - const { token: instanceToken, id, admin } = useInstanceContext() - - const { url, token } = !admin ? { - url: baseUrl, token: adminToken - } : { - url: `${baseUrl}/instances/${id}`, token: instanceToken - }; - - const updateInstance = async (instance: MerchantBackend.Instances.InstanceReconfigurationMessage): Promise<void> => { - await request(`${url}/private/`, { - method: 'patch', - token, - data: instance - }) - - if (adminToken) mutate(['/private/instances', adminToken, baseUrl], null) - mutate([`/private/`, token, url], null) - }; - - const deleteInstance = async (): Promise<void> => { - await request(`${url}/private/`, { - method: 'delete', - token: adminToken, - }) - - if (adminToken) mutate(['/private/instances', adminToken, baseUrl], null) - mutate([`/private/`, token, url], null) - } - - const clearToken = async (): Promise<void> => { - await request(`${url}/private/auth`, { - method: 'post', - token, - data: { method: 'external' } - }) - - mutate([`/private/`, token, url], null) - } - - const setNewToken = async (newToken: string): Promise<void> => { - await request(`${url}/private/auth`, { - method: 'post', - token, - data: { method: 'token', token: newToken } - }) - - mutate([`/private/`, token, url], null) - } - - return { updateInstance, deleteInstance, setNewToken, clearToken } -} - - -export function useInstanceDetails(): HttpResponse<MerchantBackend.Instances.QueryInstancesResponse> { - const { url: baseUrl, token: baseToken } = useBackendContext(); - const { token: instanceToken, id, admin } = useInstanceContext(); - - const { url, token } = !admin ? { - url: baseUrl, token: baseToken - } : { - url: `${baseUrl}/instances/${id}`, token: instanceToken - } - - const { data, error, isValidating } = useSWR<HttpResponseOk<MerchantBackend.Instances.QueryInstancesResponse>, HttpError>([`/private/`, token, url], fetcher, { - refreshInterval:0, - refreshWhenHidden: false, - revalidateOnFocus: false, - revalidateOnReconnect: false, - refreshWhenOffline: false, - errorRetryCount: 0, - errorRetryInterval: 1, - shouldRetryOnError: false, - }) - - if (isValidating) return {loading:true, data: data?.data} - if (data) return data - if (error) return error - return {loading: true} -} - -export function useManagedInstanceDetails(instanceId: string): HttpResponse<MerchantBackend.Instances.QueryInstancesResponse> { - const { url, token } = useBackendContext(); - - const { data, error, isValidating } = useSWR<HttpResponseOk<MerchantBackend.Instances.QueryInstancesResponse>, HttpError>([`/management/instances/${instanceId}`, token, url], fetcher, { - refreshInterval:0, - refreshWhenHidden: false, - revalidateOnFocus: false, - revalidateOnReconnect: false, - refreshWhenOffline: false, - errorRetryCount: 0, - errorRetryInterval: 1, - shouldRetryOnError: false, - }) - - if (isValidating) return {loading:true, data: data?.data} - if (data) return data - if (error) return error - return {loading: true} -} - -export function useBackendInstances(): HttpResponse<MerchantBackend.Instances.InstancesResponse> { - const { url } = useBackendContext() - const { token } = useInstanceContext(); - - const { data, error, isValidating } = useSWR<HttpResponseOk<MerchantBackend.Instances.InstancesResponse>, HttpError>(['/management/instances', token, url], fetcher) - - if (isValidating) return {loading:true, data: data?.data} - if (data) return data - if (error) return error - return {loading: true} -} diff --git a/packages/merchant-backend-ui/src/hooks/listener.ts b/packages/merchant-backend-ui/src/hooks/listener.ts deleted file mode 100644 index 231ed6c87..000000000 --- a/packages/merchant-backend-ui/src/hooks/listener.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { useState } from "preact/hooks"; - -/** - * returns subscriber and activator - * subscriber will receive a method (listener) that will be call when the activator runs. - * the result of calling the listener will be sent to @action - * - * @param action from <T> to <R> - * @returns activator and subscriber, undefined activator means that there is not subscriber - */ - -export function useListener<T, R = any>(action: (r: T) => Promise<R>): [undefined | (() => Promise<R>), (listener?: () => T) => void] { - type RunnerHandler = { toBeRan?: () => Promise<R>; }; - const [state, setState] = useState<RunnerHandler>({}); - - /** - * subscriber will receive a method that will be call when the activator runs - * - * @param listener function to be run when the activator runs - */ - const subscriber = (listener?: () => T) => { - if (listener) { - setState({ - toBeRan: () => { - const whatWeGetFromTheListener = listener(); - return action(whatWeGetFromTheListener); - } - }); - } else { - setState({ - toBeRan: undefined - }) - } - }; - - /** - * activator will call runner if there is someone subscribed - */ - const activator = state.toBeRan ? async () => { - if (state.toBeRan) { - return state.toBeRan(); - } - return Promise.reject(); - } : undefined; - - return [activator, subscriber]; -} diff --git a/packages/merchant-backend-ui/src/hooks/notification.ts b/packages/merchant-backend-ui/src/hooks/notification.ts deleted file mode 100644 index d1dfbff2c..000000000 --- a/packages/merchant-backend-ui/src/hooks/notification.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { useCallback, useState } from "preact/hooks"; -import { Notification } from '../utils/types'; - -interface Result { - notification?: Notification; - pushNotification: (n: Notification) => void; - removeNotification: () => void; -} - -export function useNotification(): Result { - const [notification, setNotifications] = useState<Notification|undefined>(undefined) - - const pushNotification = useCallback((n: Notification): void => { - setNotifications(n) - },[]) - - const removeNotification = useCallback(() => { - setNotifications(undefined) - },[]) - - return { notification, pushNotification, removeNotification } -} diff --git a/packages/merchant-backend-ui/src/hooks/notifications.ts b/packages/merchant-backend-ui/src/hooks/notifications.ts deleted file mode 100644 index 1c0c37308..000000000 --- a/packages/merchant-backend-ui/src/hooks/notifications.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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/> - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { useState } from "preact/hooks"; -import { Notification } from '../utils/types'; - -interface Result { - notifications: Notification[]; - pushNotification: (n: Notification) => void; - removeNotification: (n: Notification) => void; -} - -type NotificationWithDate = Notification & { since: Date } - -export function useNotifications(initial: Notification[] = [], timeout = 3000): Result { - const [notifications, setNotifications] = useState<(NotificationWithDate)[]>(initial.map(i => ({...i, since: new Date() }))) - - const pushNotification = (n: Notification): void => { - const entry = { ...n, since: new Date() } - setNotifications(ns => [...ns, entry]) - if (n.type !== 'ERROR') setTimeout(() => { - setNotifications(ns => ns.filter(x => x.since !== entry.since)) - }, timeout) - } - - const removeNotification = (notif: Notification) => { - setNotifications((ns: NotificationWithDate[]) => ns.filter(n => n !== notif)) - } - return { notifications, pushNotification, removeNotification } -} diff --git a/packages/merchant-backend-ui/src/hooks/order.ts b/packages/merchant-backend-ui/src/hooks/order.ts deleted file mode 100644 index 4a17eac30..000000000 --- a/packages/merchant-backend-ui/src/hooks/order.ts +++ /dev/null @@ -1,217 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 { useEffect, useState } from 'preact/hooks'; -import useSWR from 'swr'; -import { useBackendContext } from '../context/backend'; -import { useInstanceContext } from '../context/instance'; -import { MerchantBackend } from '../declaration'; -import { MAX_RESULT_SIZE, PAGE_SIZE } from '../utils/constants'; -import { fetcher, HttpError, HttpResponse, HttpResponseOk, HttpResponsePaginated, mutateAll, request } from './backend'; - -export interface OrderAPI { - //FIXME: add OutOfStockResponse on 410 - createOrder: (data: MerchantBackend.Orders.PostOrderRequest) => Promise<HttpResponseOk<MerchantBackend.Orders.PostOrderResponse>>; - forgetOrder: (id: string, data: MerchantBackend.Orders.ForgetRequest) => Promise<HttpResponseOk<void>>; - refundOrder: (id: string, data: MerchantBackend.Orders.RefundRequest) => Promise<HttpResponseOk<MerchantBackend.Orders.MerchantRefundResponse>>; - deleteOrder: (id: string) => Promise<HttpResponseOk<void>>; - getPaymentURL: (id: string) => Promise<HttpResponseOk<string>>; -} - -type YesOrNo = 'yes' | 'no'; - - -export function orderFetcher<T>(url: string, token: string, backend: 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 request<T>(`${backend}${url}`, { token, params }) -} - - -export function useOrderAPI(): OrderAPI { - const { url: baseUrl, token: adminToken } = useBackendContext() - const { token: instanceToken, id, admin } = useInstanceContext() - - const { url, token } = !admin ? { - url: baseUrl, token: adminToken - } : { - url: `${baseUrl}/instances/${id}`, token: instanceToken - } - - const createOrder = async (data: MerchantBackend.Orders.PostOrderRequest): Promise<HttpResponseOk<MerchantBackend.Orders.PostOrderResponse>> => { - const res = await request<MerchantBackend.Orders.PostOrderResponse>(`${url}/private/orders`, { - method: 'post', - token, - data - }) - await mutateAll(/@"\/private\/orders"@/) - return res - } - const refundOrder = async (orderId: string, data: MerchantBackend.Orders.RefundRequest): Promise<HttpResponseOk<MerchantBackend.Orders.MerchantRefundResponse>> => { - mutateAll(/@"\/private\/orders"@/) - return request<MerchantBackend.Orders.MerchantRefundResponse>(`${url}/private/orders/${orderId}/refund`, { - method: 'post', - token, - data - }) - - // return res - } - - const forgetOrder = async (orderId: string, data: MerchantBackend.Orders.ForgetRequest): Promise<HttpResponseOk<void>> => { - mutateAll(/@"\/private\/orders"@/) - return request(`${url}/private/orders/${orderId}/forget`, { - method: 'patch', - token, - data - }) - - } - const deleteOrder = async (orderId: string): Promise<HttpResponseOk<void>> => { - mutateAll(/@"\/private\/orders"@/) - return request(`${url}/private/orders/${orderId}`, { - method: 'delete', - token - }) - } - - const getPaymentURL = async (orderId: string): Promise<HttpResponseOk<string>> => { - return request<MerchantBackend.Orders.MerchantOrderStatusResponse>(`${url}/private/orders/${orderId}`, { - method: 'get', - token - }).then((res) => { - const url = res.data.order_status === "unpaid" ? res.data.taler_pay_uri : res.data.contract_terms.fulfillment_url - const response: HttpResponseOk<string> = res as any - response.data = url || '' - return response - }) - } - - return { createOrder, forgetOrder, deleteOrder, refundOrder, getPaymentURL } -} - -export function useOrderDetails(oderId: string): HttpResponse<MerchantBackend.Orders.MerchantOrderStatusResponse> { - const { url: baseUrl, token: baseToken } = useBackendContext(); - const { token: instanceToken, id, admin } = useInstanceContext(); - - const { url, token } = !admin ? { - url: baseUrl, token: baseToken - } : { - url: `${baseUrl}/instances/${id}`, token: instanceToken - }; - - const { data, error, isValidating } = useSWR<HttpResponseOk<MerchantBackend.Orders.MerchantOrderStatusResponse>, HttpError>([`/private/orders/${oderId}`, token, url], fetcher, { - refreshInterval: 0, - refreshWhenHidden: false, - revalidateOnFocus: false, - revalidateOnReconnect: false, - refreshWhenOffline: false, - }) - - if (isValidating) return { loading: true, data: data?.data } - if (data) return data - if (error) return error - return { loading: true } -} - -export interface InstanceOrderFilter { - paid?: YesOrNo; - refunded?: YesOrNo; - wired?: YesOrNo; - date?: Date; -} - -export function useInstanceOrders(args?: InstanceOrderFilter, updateFilter?: (d: Date) => void): HttpResponsePaginated<MerchantBackend.Orders.OrderHistory> { - const { url: baseUrl, token: baseToken } = useBackendContext(); - const { token: instanceToken, id, admin } = useInstanceContext(); - - const { url, token } = !admin ? { - url: baseUrl, token: baseToken - } : { - url: `${baseUrl}/instances/${id}`, token: instanceToken - } - - const [pageBefore, setPageBefore] = useState(1) - const [pageAfter, setPageAfter] = useState(1) - - const totalAfter = pageAfter * PAGE_SIZE; - const totalBefore = args?.date ? pageBefore * PAGE_SIZE : 0; - - /** - * FIXME: this can be cleaned up a little - * - * the logic of double query should be inside the orderFetch so from the hook perspective and cache - * is just one query and one error status - */ - const { data: beforeData, error: beforeError, isValidating: loadingBefore } = useSWR<HttpResponseOk<MerchantBackend.Orders.OrderHistory>, HttpError>( - [`/private/orders`, token, url, args?.paid, args?.refunded, args?.wired, args?.date, totalBefore], - orderFetcher, - ) - const { data: afterData, error: afterError, isValidating: loadingAfter } = useSWR<HttpResponseOk<MerchantBackend.Orders.OrderHistory>, HttpError>( - [`/private/orders`, token, url, args?.paid, args?.refunded, args?.wired, args?.date, -totalAfter], - orderFetcher, - ) - - //this will save last result - const [lastBefore, setLastBefore] = useState<HttpResponse<MerchantBackend.Orders.OrderHistory>>({ loading: true }) - const [lastAfter, setLastAfter] = useState<HttpResponse<MerchantBackend.Orders.OrderHistory>>({ loading: true }) - useEffect(() => { - if (afterData) setLastAfter(afterData) - if (beforeData) setLastBefore(beforeData) - }, [afterData, beforeData]) - - // this has problems when there are some ids missing - - if (beforeError) return beforeError - if (afterError) return afterError - - - const pagination = { - isReachingEnd: afterData && afterData.data.orders.length < totalAfter, - isReachingStart: (!args?.date) || (beforeData && beforeData.data.orders.length < totalBefore), - loadMore: () => { - if (!afterData) return - if (afterData.data.orders.length < MAX_RESULT_SIZE) { - setPageAfter(pageAfter + 1) - } else { - const from = afterData.data.orders[afterData.data.orders.length - 1].timestamp.t_s - if (from && updateFilter) updateFilter(new Date(from)) - } - }, - loadMorePrev: () => { - if (!beforeData) return - if (beforeData.data.orders.length < MAX_RESULT_SIZE) { - setPageBefore(pageBefore + 1) - } else if (beforeData) { - const from = beforeData.data.orders[beforeData.data.orders.length - 1].timestamp.t_s - if (from && updateFilter) updateFilter(new Date(from)) - } - }, - } - - const orders = !beforeData || !afterData ? [] : (beforeData || lastBefore).data.orders.slice().reverse().concat((afterData || lastAfter).data.orders) - if (loadingAfter || loadingBefore) return { loading: true, data: { orders } } - if (beforeData && afterData) { - return { ok: true, data: { orders }, ...pagination } - } - return { loading: true } - -} - diff --git a/packages/merchant-backend-ui/src/hooks/product.ts b/packages/merchant-backend-ui/src/hooks/product.ts deleted file mode 100644 index 4fc8bccb7..000000000 --- a/packages/merchant-backend-ui/src/hooks/product.ts +++ /dev/null @@ -1,223 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 { useEffect } from "preact/hooks"; -import useSWR, { trigger, useSWRInfinite, cache, mutate } from "swr"; -import { useBackendContext } from "../context/backend"; -// import { useFetchContext } from '../context/fetch'; -import { useInstanceContext } from "../context/instance"; -import { MerchantBackend, WithId } from "../declaration"; -import { - fetcher, - HttpError, - HttpResponse, - HttpResponseOk, - mutateAll, - request, -} from "./backend"; - -export interface ProductAPI { - createProduct: ( - data: MerchantBackend.Products.ProductAddDetail - ) => Promise<void>; - updateProduct: ( - id: string, - data: MerchantBackend.Products.ProductPatchDetail - ) => Promise<void>; - deleteProduct: (id: string) => Promise<void>; - lockProduct: ( - id: string, - data: MerchantBackend.Products.LockRequest - ) => Promise<void>; -} - -export function useProductAPI(): ProductAPI { - const { url: baseUrl, token: adminToken } = useBackendContext(); - const { token: instanceToken, id, admin } = useInstanceContext(); - - const { url, token } = !admin - ? { - url: baseUrl, - token: adminToken, - } - : { - url: `${baseUrl}/instances/${id}`, - token: instanceToken, - }; - - const createProduct = async ( - data: MerchantBackend.Products.ProductAddDetail - ): Promise<void> => { - await request(`${url}/private/products`, { - method: "post", - token, - data, - }); - - await mutateAll(/@"\/private\/products"@/, null); - }; - - const updateProduct = async ( - productId: string, - data: MerchantBackend.Products.ProductPatchDetail - ): Promise<void> => { - const r = await request(`${url}/private/products/${productId}`, { - method: "patch", - token, - data, - }); - - await mutateAll(/@"\/private\/products\/.*"@/); - return Promise.resolve(); - }; - - const deleteProduct = async (productId: string): Promise<void> => { - await request(`${url}/private/products/${productId}`, { - method: "delete", - token, - }); - - await mutateAll(/@"\/private\/products"@/); - }; - - const lockProduct = async ( - productId: string, - data: MerchantBackend.Products.LockRequest - ): Promise<void> => { - await request(`${url}/private/products/${productId}/lock`, { - method: "post", - token, - data, - }); - - await mutateAll(/@"\/private\/products"@/); - }; - - return { createProduct, updateProduct, deleteProduct, lockProduct }; -} - -export function useInstanceProducts(): HttpResponse< - (MerchantBackend.Products.ProductDetail & WithId)[] -> { - const { url: baseUrl, token: baseToken } = useBackendContext(); - const { token: instanceToken, id, admin } = useInstanceContext(); - // const { useSWR, useSWRInfinite } = useFetchContext(); - - const { url, token } = !admin - ? { - url: baseUrl, - token: baseToken, - } - : { - url: `${baseUrl}/instances/${id}`, - token: instanceToken, - }; - - const { - data: list, - error: listError, - isValidating: listLoading, - } = useSWR< - HttpResponseOk<MerchantBackend.Products.InventorySummaryResponse>, - HttpError - >([`/private/products`, token, url], fetcher, { - refreshInterval: 0, - refreshWhenHidden: false, - revalidateOnFocus: false, - revalidateOnReconnect: false, - refreshWhenOffline: false, - }); - - const { - data: products, - error: productError, - setSize, - size, - } = useSWRInfinite< - HttpResponseOk<MerchantBackend.Products.ProductDetail>, - HttpError - >( - (pageIndex: number) => { - if (!list?.data || !list.data.products.length || listError || listLoading) - return null; - return [ - `/private/products/${list.data.products[pageIndex].product_id}`, - token, - url, - ]; - }, - fetcher, - { - revalidateAll: true, - } - ); - - useEffect(() => { - if (list?.data && list.data.products.length > 0) { - setSize(list.data.products.length); - } - }, [list?.data.products.length, listLoading]); - - if (listLoading) return { loading: true, data: [] }; - if (listError) return listError; - if (productError) return productError; - if (list?.data && list.data.products.length === 0) { - return { ok: true, data: [] }; - } - if (products) { - const dataWithId = products.map((d) => { - //take the id from the queried url - return { - ...d.data, - id: d.info?.url.replace(/.*\/private\/products\//, "") || "", - }; - }); - return { ok: true, data: dataWithId }; - } - return { loading: true }; -} - -export function useProductDetails( - productId: string -): HttpResponse<MerchantBackend.Products.ProductDetail> { - const { url: baseUrl, token: baseToken } = useBackendContext(); - const { token: instanceToken, id, admin } = useInstanceContext(); - - const { url, token } = !admin - ? { - url: baseUrl, - token: baseToken, - } - : { - url: `${baseUrl}/instances/${id}`, - token: instanceToken, - }; - - const { data, error, isValidating } = useSWR< - HttpResponseOk<MerchantBackend.Products.ProductDetail>, - HttpError - >([`/private/products/${productId}`, token, url], fetcher, { - refreshInterval: 0, - refreshWhenHidden: false, - revalidateOnFocus: false, - revalidateOnReconnect: false, - refreshWhenOffline: false, - }); - - if (isValidating) return { loading: true, data: data?.data }; - if (data) return data; - if (error) return error; - return { loading: true }; -} diff --git a/packages/merchant-backend-ui/src/hooks/tips.ts b/packages/merchant-backend-ui/src/hooks/tips.ts deleted file mode 100644 index 345e1faa5..000000000 --- a/packages/merchant-backend-ui/src/hooks/tips.ts +++ /dev/null @@ -1,159 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 { useBackendContext } from '../context/backend'; -import { useInstanceContext } from '../context/instance'; -import { MerchantBackend } from '../declaration'; -import { fetcher, HttpError, HttpResponse, HttpResponseOk, mutateAll, request } from './backend'; - - -export function useReservesAPI(): ReserveMutateAPI { - const { url: baseUrl, token: adminToken } = useBackendContext(); - const { token: instanceToken, id, admin } = useInstanceContext(); - - const { url, token } = !admin ? { - url: baseUrl, token: adminToken - } : { - url: `${baseUrl}/instances/${id}`, token: instanceToken - }; - - const createReserve = async (data: MerchantBackend.Tips.ReserveCreateRequest): Promise<HttpResponseOk<MerchantBackend.Tips.ReserveCreateConfirmation>> => { - const res = await request<MerchantBackend.Tips.ReserveCreateConfirmation>(`${url}/private/reserves`, { - method: 'post', - token, - data - }); - - await mutateAll(/@"\/private\/reserves"@/); - - return res - }; - - const authorizeTipReserve = async (pub: string, data: MerchantBackend.Tips.TipCreateRequest): Promise<HttpResponseOk<MerchantBackend.Tips.TipCreateConfirmation>> => { - const res = await request<MerchantBackend.Tips.TipCreateConfirmation>(`${url}/private/reserves/${pub}/authorize-tip`, { - method: 'post', - token, - data - }); - await mutateAll(/@"\/private\/reserves"@/); - - return res - }; - - const authorizeTip = async (data: MerchantBackend.Tips.TipCreateRequest): Promise<HttpResponseOk<MerchantBackend.Tips.TipCreateConfirmation>> => { - const res = await request<MerchantBackend.Tips.TipCreateConfirmation>(`${url}/private/tips`, { - method: 'post', - token, - data - }); - - await mutateAll(/@"\/private\/reserves"@/); - - return res - }; - - const deleteReserve = async (pub: string): Promise<HttpResponse<void>> => { - const res = await request<void>(`${url}/private/reserves/${pub}`, { - method: 'delete', - token, - }); - - await mutateAll(/@"\/private\/reserves"@/); - - return res - }; - - - return { createReserve, authorizeTip, authorizeTipReserve, deleteReserve }; -} - -export interface ReserveMutateAPI { - createReserve: (data: MerchantBackend.Tips.ReserveCreateRequest) => Promise<HttpResponseOk<MerchantBackend.Tips.ReserveCreateConfirmation>>; - authorizeTipReserve: (id: string, data: MerchantBackend.Tips.TipCreateRequest) => Promise<HttpResponseOk<MerchantBackend.Tips.TipCreateConfirmation>>; - authorizeTip: (data: MerchantBackend.Tips.TipCreateRequest) => Promise<HttpResponseOk<MerchantBackend.Tips.TipCreateConfirmation>>; - deleteReserve: (id: string) => Promise<HttpResponse<void>>; -} - -export function useInstanceTips(): HttpResponse<MerchantBackend.Tips.TippingReserveStatus> { - const { url: baseUrl, token: baseToken } = useBackendContext(); - const { token: instanceToken, id, admin } = useInstanceContext(); - - const { url, token } = !admin ? { - url: baseUrl, token: baseToken - } : { - url: `${baseUrl}/instances/${id}`, token: instanceToken - } - - const { data, error, isValidating } = useSWR<HttpResponseOk<MerchantBackend.Tips.TippingReserveStatus>, HttpError>([`/private/reserves`, token, url], fetcher) - - if (isValidating) return { loading: true, data: data?.data } - if (data) return data - if (error) return error - return { loading: true } -} - - -export function useReserveDetails(reserveId: string): HttpResponse<MerchantBackend.Tips.ReserveDetail> { - const { url: baseUrl } = useBackendContext(); - const { token, id: instanceId, admin } = useInstanceContext(); - - const url = !admin ? baseUrl : `${baseUrl}/instances/${instanceId}` - - const { data, error, isValidating } = useSWR<HttpResponseOk<MerchantBackend.Tips.ReserveDetail>, HttpError>([`/private/reserves/${reserveId}`, token, url], reserveDetailFetcher, { - refreshInterval:0, - refreshWhenHidden: false, - revalidateOnFocus: false, - revalidateOnReconnect: false, - refreshWhenOffline: false, - }) - - if (isValidating) return { loading: true, data: data?.data } - if (data) return data - if (error) return error - return { loading: true } -} - -export function useTipDetails(tipId: string): HttpResponse<MerchantBackend.Tips.TipDetails> { - const { url: baseUrl } = useBackendContext(); - const { token, id: instanceId, admin } = useInstanceContext(); - - const url = !admin ? baseUrl : `${baseUrl}/instances/${instanceId}` - - const { data, error, isValidating } = useSWR<HttpResponseOk<MerchantBackend.Tips.TipDetails>, HttpError>([`/private/tips/${tipId}`, token, url], tipsDetailFetcher, { - refreshInterval:0, - refreshWhenHidden: false, - revalidateOnFocus: false, - revalidateOnReconnect: false, - refreshWhenOffline: false, - }) - - if (isValidating) return { loading: true, data: data?.data } - if (data) return data - if (error) return error - return { loading: true } -} - -export function reserveDetailFetcher<T>(url: string, token: string, backend: string): Promise<HttpResponseOk<T>> { - return request<T>(`${backend}${url}`, { token, params: { - tips: 'yes' - } }) -} - -export function tipsDetailFetcher<T>(url: string, token: string, backend: string): Promise<HttpResponseOk<T>> { - return request<T>(`${backend}${url}`, { token, params: { - pickups: 'yes' - } }) -} diff --git a/packages/merchant-backend-ui/src/hooks/transfer.ts b/packages/merchant-backend-ui/src/hooks/transfer.ts deleted file mode 100644 index 482f00dc5..000000000 --- a/packages/merchant-backend-ui/src/hooks/transfer.ts +++ /dev/null @@ -1,150 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 { MerchantBackend } from '../declaration'; -import { useBackendContext } from '../context/backend'; -import { request, mutateAll, HttpResponse, HttpError, HttpResponseOk, HttpResponsePaginated } from './backend'; -import useSWR from 'swr'; -import { useInstanceContext } from '../context/instance'; -import { MAX_RESULT_SIZE, PAGE_SIZE } from '../utils/constants'; -import { useEffect, useState } from 'preact/hooks'; - -async function transferFetcher<T>(url: string, token: string, backend: 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) { - // if (delta > 0) { - // params.after = searchDate?.getTime() - // } else { - // params.before = searchDate?.getTime() - // } - params.limit = delta - } - if (position !== undefined) params.offset = position - - return request<T>(`${backend}${url}`, { token, params }) -} - -export function useTransferAPI(): TransferAPI { - const { url: baseUrl, token: adminToken } = useBackendContext(); - const { token: instanceToken, id, admin } = useInstanceContext(); - - const { url, token } = !admin ? { - url: baseUrl, token: adminToken - } : { - url: `${baseUrl}/instances/${id}`, token: instanceToken - }; - - const informTransfer = async (data: MerchantBackend.Transfers.TransferInformation): Promise<HttpResponseOk<MerchantBackend.Transfers.MerchantTrackTransferResponse>> => { - mutateAll(/@"\/private\/transfers"@/); - - return request<MerchantBackend.Transfers.MerchantTrackTransferResponse>(`${url}/private/transfers`, { - method: 'post', - token, - data - }); - }; - - return { informTransfer }; -} - -export interface TransferAPI { - informTransfer: (data: MerchantBackend.Transfers.TransferInformation) => Promise<HttpResponseOk<MerchantBackend.Transfers.MerchantTrackTransferResponse>>; -} - -export interface InstanceTransferFilter { - payto_uri?: string; - verified?: 'yes' | 'no'; - position?: string; -} - - -export function useInstanceTransfers(args?: InstanceTransferFilter, updatePosition?: (id: string) => void): HttpResponsePaginated<MerchantBackend.Transfers.TransferList> { - const { url: baseUrl, token: baseToken } = useBackendContext(); - const { token: instanceToken, id, admin } = useInstanceContext(); - - const { url, token } = !admin ? { - url: baseUrl, token: baseToken - } : { - url: `${baseUrl}/instances/${id}`, token: instanceToken - } - - const [pageBefore, setPageBefore] = useState(1) - const [pageAfter, setPageAfter] = useState(1) - - const totalAfter = pageAfter * PAGE_SIZE; - const totalBefore = args?.position !== undefined ? pageBefore * PAGE_SIZE : 0; - - /** - * FIXME: this can be cleaned up a little - * - * the logic of double query should be inside the orderFetch so from the hook perspective and cache - * is just one query and one error status - */ - const { data: beforeData, error: beforeError, isValidating: loadingBefore } = useSWR<HttpResponseOk<MerchantBackend.Transfers.TransferList>, HttpError>( - [`/private/transfers`, token, url, args?.payto_uri, args?.verified, args?.position, totalBefore], - transferFetcher, - ) - const { data: afterData, error: afterError, isValidating: loadingAfter } = useSWR<HttpResponseOk<MerchantBackend.Transfers.TransferList>, HttpError>( - [`/private/transfers`, token, url, args?.payto_uri, args?.verified, args?.position, -totalAfter], - transferFetcher, - ) - - //this will save last result - const [lastBefore, setLastBefore] = useState<HttpResponse<MerchantBackend.Transfers.TransferList>>({ loading: true }) - const [lastAfter, setLastAfter] = useState<HttpResponse<MerchantBackend.Transfers.TransferList>>({ loading: true }) - useEffect(() => { - if (afterData) setLastAfter(afterData) - if (beforeData) setLastBefore(beforeData) - }, [afterData, beforeData]) - - // this has problems when there are some ids missing - - if (beforeError) return beforeError - if (afterError) return afterError - - const pagination = { - isReachingEnd: afterData && afterData.data.transfers.length < totalAfter, - isReachingStart: (!args?.position) || (beforeData && beforeData.data.transfers.length < totalBefore), - loadMore: () => { - if (!afterData) return - if (afterData.data.transfers.length < MAX_RESULT_SIZE) { - setPageAfter(pageAfter + 1) - } else { - const from = `${afterData.data.transfers[afterData.data.transfers.length - 1].transfer_serial_id}` - if (from && updatePosition) updatePosition(from) - } - }, - loadMorePrev: () => { - if (!beforeData) return - if (beforeData.data.transfers.length < MAX_RESULT_SIZE) { - setPageBefore(pageBefore + 1) - } else if (beforeData) { - const from = `${beforeData.data.transfers[beforeData.data.transfers.length - 1].transfer_serial_id}` - if (from && updatePosition) updatePosition(from) - } - }, - } - - const transfers = !beforeData || !afterData ? [] : (beforeData || lastBefore).data.transfers.slice().reverse().concat((afterData || lastAfter).data.transfers) - if (loadingAfter || loadingBefore) return { loading: true, data: { transfers } } - if (beforeData && afterData) { - return { ok: true, data: { transfers }, ...pagination } - } - return { loading: true } -} - - |