This commit is contained in:
Sean Edmunds
2025-04-15 20:21:25 -05:00
parent 2e72b76e95
commit 78dec160b6
34 changed files with 225 additions and 466 deletions

View File

@@ -1,22 +1,29 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useCallback, useRef } from 'react';
import { useRef } from 'react';
import { Flip, toast } from 'react-toastify';
const url = `${import.meta.env.PROD ? import.meta.env.VITE_DOMAIN_PROD : import.meta.env.VITE_DOMAIN_DEV}/${import.meta.env.VITE_BASE_URL}`;
const 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 callEndpoint = async (url: string) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`${response.status} ${response.statusText}`);
}
return await response.json();
};
export function useQueryAPI({ endpoint }: { endpoint: string }) {
const callEndpoint = async (endpoint: string) => {
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',
},
headers: headers,
};
const response = await fetch(`${url}/${endpoint}`, options);
@@ -44,15 +51,7 @@ export function useMutationAPI() {
) => {
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',
},
headers: headers,
body: JSON.stringify(body),
};
notify();
@@ -124,7 +123,7 @@ export function useMutationAPI() {
endpoint: string;
body: object | undefined | null;
}) => callEndpoint(method, endpoint, body),
onMutate: ({ method, endpoint }) => {
onMutate: ({ endpoint }) => {
queryClient.refetchQueries({
queryKey: ['GET', endpoint],
});
@@ -135,7 +134,7 @@ export function useMutationAPI() {
endpoint === 'api/applicationOrder' &&
queryClient.refetchQueries({ queryKey: ['GET', 'api/applicationOrder'] });
},
onSuccess: (data, { method, endpoint }) => {
onSuccess: (_, { endpoint }) => {
queryClient.refetchQueries({
queryKey: ['GET', endpoint],
});
@@ -180,17 +179,10 @@ export function useWeatherAPI({
} else if (endpoint === 'stations') {
url = `https://api.weather.gov/gridpoints/${office}/${x},${y}/stations`;
}
const callEndpoint = async () => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`${response.status} ${response.statusText}`);
}
return await response.json();
};
return useQuery({
queryKey: ['GET', url],
queryFn: () => callEndpoint(),
queryFn: () => callEndpoint(url),
});
}
@@ -200,16 +192,10 @@ export function useSunStateAPI({ lat, lon }: { lat: number; lon: number }) {
date.setDate(date.getDate() + 1);
const tomorrow = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
const url = `https://api.sunrisesunset.io/json?lat=${lat}&lng=${lon}&date_start=${today}&date_end=${tomorrow}`;
const callEndpoint = async () => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`${response.status} ${response.statusText}`);
}
return await response.json();
};
return useQuery({
queryKey: ['GET', url],
queryFn: () => callEndpoint(),
queryFn: () => callEndpoint(url),
});
}
@@ -238,15 +224,7 @@ export function useFileUploadAPI() {
) => {
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',
},
headers: headers,
body: body ?? undefined,
};
const response = await fetch(url, options as unknown as Body);
@@ -300,115 +278,3 @@ export function useFileUploadAPI() {
},
});
}
export function useFetchAllRecordsAPI({ endpoint }: { endpoint: string }) {
const pageSize = 500;
const queryClient = useQueryClient();
const fetchPage = async (page: number, retryCount = 0): Promise<any> => {
// Start building the URL
const urlObject = new URL(`${url}/${endpoint}`);
// Check and append custom query parameters from the endpoint
const customParams = new URLSearchParams(endpoint.split('?')[1]);
for (const [key, value] of customParams) {
if (!urlObject.searchParams.has(key)) {
urlObject.searchParams.append(key, value);
}
}
// Add 'page' and 'size' parameters
urlObject.searchParams.set('page', page.toString()); // Use 'set' to ensure uniqueness
urlObject.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,
};
}

View File

@@ -8,11 +8,11 @@ export function useCommonHooks() {
const { mutateAsync } = useMutationAPI();
const isMobile = window.matchMedia('(max-width: 600px)').matches;
const [theme, setTheme] = useAtom(appTheme);
const [themeName, setThemeName] = useLocalStorage<any>({
const [themeName, setThemeName] = useLocalStorage<string>({
key: 'theme',
defaultValue: 'blackboard',
});
const [appFilter, setAppFilter] = useLocalStorage<[any]>({
const [appFilter, setAppFilter] = useLocalStorage<[string]>({
key: 'appFilter',
defaultValue: ['All'],
});
@@ -24,16 +24,16 @@ export function useCommonHooks() {
key: 'bookmarkSort',
defaultValue: 'A-Z',
});
const [bookmarkFilter, setBookmarkFilter] = useLocalStorage<[any]>({
const [bookmarkFilter, setBookmarkFilter] = useLocalStorage<[string]>({
key: 'bookmarkFilter',
defaultValue: ['All'],
});
const [currentTheme, setCurrentTheme] = useLocalStorage<any>({
const [currentTheme, setCurrentTheme] = useLocalStorage<string>({
key: 'theme',
defaultValue: 'blackboard',
});
const [currentThemePlaylist, setCurrentThemePlaylist] = useLocalStorage<any>({
const [currentThemePlaylist, setCurrentThemePlaylist] = useLocalStorage<string>({
key: 'themePlaylist',
defaultValue: 'All',
});
@@ -80,6 +80,13 @@ export function useCommonHooks() {
'i',
); // fragment locator
const reorder = (list: any, startIndex: any, endIndex: any) => {
const result = Array.from(list);
const [removed]: any = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
const handleThemeChange = (themes: any, save: any) => {
const enabled = themes
.filter((t: any) => t.randomEnabled)
@@ -116,6 +123,7 @@ export function useCommonHooks() {
modalStyle,
urlPattern,
fixDigits,
reorder,
handleThemeChange,
handleDelete,
themeName,