import { useInfiniteQuery, useMutation, useQuery, useQueryClient, } from '@tanstack/react-query'; import { useCallback } from 'react'; import { toast } from 'react-toastify'; export function useQueryAPI({ endpoint }: { endpoint: string }) { const callEndpoint = async (endpoint: string) => { const url = `http://localhost:5000/trigger/${endpoint}`; const options: object = { method: 'GET', headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': '*', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 'Access-Control-Allow-Credentials': true, 'Sec-Fetch-Mode': 'no-cors', 'Sec-Fetch-Site': 'same-site', }, }; const response = await fetch(url, options); if (!response.ok) { throw new Error(`${response.status} ${response.statusText}`); } return await response.json(); }; return useQuery({ queryKey: ['GET', endpoint], queryFn: () => callEndpoint(endpoint), }); } export function useMutationAPI() { const callEndpoint = async ( method: string, endpoint: string, body: object | undefined | null, ) => { const url = `http://localhost:5000/trigger/${endpoint}`; const options: any = { method: method, headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': '*', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 'Access-Control-Allow-Credentials': true, 'Sec-Fetch-Mode': 'no-cors', 'Sec-Fetch-Site': 'same-site', }, body: JSON.stringify(body), }; const response = await fetch(url, options); if (!response.ok) { const responseData = await response.json(); if (typeof responseData.detail !== 'string') { const errorResponse = JSON.stringify({ status: response.status, statusText: response.statusText, method: options.method, endpoint: endpoint, msg: responseData.detail[0]?.msg, loc: responseData.detail[0]?.loc, type: responseData.detail[0]?.type, }); throw new Error(errorResponse); } if (typeof responseData.detail === 'object') { const errorResponse = JSON.stringify({ status: response.status, statusText: response.statusText, method: options.method, endpoint: endpoint, msg: responseData.detail.message, }); throw new Error(errorResponse); } const errorResponse = JSON.stringify({ status: response.status, statusText: response.statusText, method: options.method, endpoint: endpoint, msg: responseData.detail, }); throw new Error(errorResponse); } return await response.json(); }; const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ method, endpoint, body = undefined, }: { method: string; endpoint: string; body?: object | undefined | null; }) => callEndpoint(method, endpoint, body), onMutate: () => {}, onSuccess: (data, variables) => { queryClient.invalidateQueries({ queryKey: ['GET', variables.endpoint] }); if (variables.endpoint.includes('/')) { if ((variables.endpoint.match(/\//g) || []).length > 2) { const GetEndpoint = variables.endpoint.split('/'); const Endpoint = `${GetEndpoint[1]}/${GetEndpoint[2]}`; queryClient.invalidateQueries({ queryKey: ['GET', `/${Endpoint}`] }); } else { const GetEndpoint = variables.endpoint.split('/')[1]; queryClient.invalidateQueries({ queryKey: ['GET', `/${GetEndpoint}`], }); } } }, }); } export function useFileUploadAPI() { const callEndpoint = async ( method: string, endpoint: string, body?: object | FormData | undefined | null, ) => { const url = `http://localhost:5000/trigger/${endpoint}`; const options = { method: method, headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': '*', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 'Access-Control-Allow-Credentials': true, 'Sec-Fetch-Mode': 'no-cors', 'Sec-Fetch-Site': 'same-site', }, body: body ?? undefined, }; const response = await fetch(url, options as unknown as Body); if (!response.ok) { const responseData = await response.json(); if (typeof responseData.detail !== 'string') { const errorResponse = JSON.stringify({ status: response.status, statusText: response.statusText, method: options.method, endpoint: endpoint, msg: responseData.detail[0].msg, loc: responseData.detail[0].loc, type: responseData.detail[0].type, }); throw new Error(errorResponse); } const errorResponse = JSON.stringify({ status: response.status, statusText: response.statusText, method: options.method, endpoint: endpoint, msg: responseData.detail, }); throw new Error(errorResponse); } return await response.json(); }; const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ method, endpoint, body, }: { method: string; endpoint: string; body: object | FormData | undefined | null; toastID?: string; }) => callEndpoint(method, endpoint, body), onMutate: () => {}, onSuccess: (variables) => { queryClient.invalidateQueries({ queryKey: ['GET', variables.endpoint] }); if (variables.endpoint?.includes('/')) { const GetEndpoint = variables.endpoint.split('/')[1]; queryClient.invalidateQueries({ queryKey: ['GET', `/${GetEndpoint}`] }); } }, }); } export function useFetchAllRecordsAPI({ endpoint }: { endpoint: string }) { const pageSize = 500; const queryClient = useQueryClient(); const fetchPage = async (page: number, retryCount = 0): Promise => { // Start building the URL const url = new URL(`http://localhost:5000/trigger/${endpoint}`); // Check and append custom query parameters from the endpoint const customParams = new URLSearchParams(endpoint.split('?')[1]); for (const [key, value] of customParams) { if (!url.searchParams.has(key)) { url.searchParams.append(key, value); } } // Add 'page' and 'size' parameters url.searchParams.set('page', page.toString()); // Use 'set' to ensure uniqueness url.searchParams.set('size', pageSize.toString()); const options: any = { method: 'GET', headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': '*', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 'Access-Control-Allow-Credentials': true, 'Sec-Fetch-Mode': 'no-cors', 'Sec-Fetch-Site': 'same-site', }, }; try { const response = await fetch(url.toString(), options); if (!response.ok) { // If the response is not ok and status is 401 (not authorized), reload the page if (response.status === 401) { window.location.reload(); } throw new Error( `Error fetching page ${page}: ${response.status} ${response.statusText}`, ); } return response.json(); } catch (err) { if (retryCount < 3) { // Retry up to 3 times with a random delay const delay = Math.floor(Math.random() * 3000) + 1000; // Random delay between 1 to 3 seconds console.error( `Retrying page ${page}, attempt ${retryCount + 1} after ${delay} ms`, ); await new Promise((resolve) => setTimeout(resolve, delay)); return fetchPage(page, retryCount + 1); } console.error(`Error fetching page ${page} after 3 retries:`, err); throw err; // Re-throw the error to handle it in the calling function } }; const callEndpoint = async () => { try { // Fetch the first page to get the total number of items and pages const firstPageData = await fetchPage(1); // If the first page doesn't have a 'pages' property, return the data as-is if (firstPageData?.pages === undefined) { return firstPageData; } if (firstPageData?.pages === 1) { return firstPageData.items; } let totalPages = firstPageData.pages; const allItems = [...firstPageData.items]; // Fetch each page sequentially for (let page = 2; page <= totalPages; page++) { try { const pageData = await fetchPage(page); allItems.push(...pageData.items); // Check and update the totalPages if it has increased if (pageData.pages > totalPages) { totalPages = pageData.pages; } } catch (err) { console.error(`Final error fetching page ${page}:`, err); // Continue to the next page even if the current page fails } } return allItems; } catch (error) { // Handling errors in fetching the first page console.error('Error fetching the first page:', error); return []; // Return empty array on error } }; const invalidate = useCallback(() => { queryClient.invalidateQueries({ queryKey: ['GET', endpoint] }); }, [queryClient, endpoint]); return { ...useQuery({ queryKey: ['GET', endpoint], queryFn: callEndpoint, }), invalidate, }; } interface DownloadFileOptions { endpoint: string; file_name: string; } export function useDownloadFile() { const downloadFile = useCallback( async ({ endpoint, file_name }: DownloadFileOptions) => { const url = `http://localhost:5000/trigger/${endpoint}`; const options: any = { method: 'GET', headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': '*', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 'Access-Control-Allow-Credentials': true, 'Sec-Fetch-Mode': 'no-cors', 'Sec-Fetch-Site': 'same-site', }, }; const toastID = toast.loading('Processing...'); try { const response = await fetch(url, options); if (!response.ok) { throw new Error('Network response was not ok'); } const blob = await response.blob(); if (blob.type === 'application/json') { const blob_text = await blob.text(); const cleaned_text = blob_text.replace(/"/g, ''); toast.update(toastID, { render: cleaned_text, type: 'info', isLoading: false, autoClose: 5000, closeButton: null, }); return; } if (typeof window !== 'undefined') { const blobURL = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.style.display = 'none'; a.href = blobURL; a.download = file_name; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(blobURL); document.body.removeChild(a); toast.update(toastID, { render: `${file_name} Downloaded`, type: 'success', isLoading: false, autoClose: 5000, closeButton: null, }); } } catch (error) { console.error('Download failed:', error); toast.update(toastID, { render: `Error Downloading ${file_name}. Please try again.`, type: 'error', isLoading: false, autoClose: 5000, closeButton: null, }); } }, [], ); return downloadFile; } export const useFetchInfiniteQuery = ({ endpoint, pageSize = 10, }: { endpoint: string; pageSize?: number; }) => { const queryKey = ['GET', endpoint]; const fetchInfiniteQuery = async (endpoint: string) => { const url = `http://localhost:5000/trigger/${endpoint}`; const requestOptions: any = { method: 'GET', headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': '*', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 'Access-Control-Allow-Credentials': true, 'Sec-Fetch-Mode': 'no-cors', 'Sec-Fetch-Site': 'same-site', }, }; try { const response = await fetch(url, requestOptions); return await response.json(); } catch (error) { console.error('Error fetching transactions:', error); throw error; } }; const queryFn = ({ pageParam = 1 }) => fetchInfiniteQuery(`${endpoint}&page=${pageParam}&size=${pageSize}`); const getNextPageParam = (lastPage: any, allPages: any) => { if (lastPage.items.length < pageSize) { return undefined; } return allPages.length + 1; }; const { data, isLoading, isFetchingNextPage, fetchNextPage, isError, isFetching } = useInfiniteQuery({ queryKey, queryFn, getNextPageParam, initialPageParam: 1, }); return { data, isLoading, isFetchingNextPage, fetchNextPage, isError, isFetching, }; };