From 78dec160b645cdebd21319bd342247a190579ac0 Mon Sep 17 00:00:00 2001 From: Sean Edmunds Date: Tue, 15 Apr 2025 20:21:25 -0500 Subject: [PATCH] Typing --- package-lock.json | 22 --- package.json | 1 - src/App.tsx | 3 +- src/components/HelpModal/HelpModal.tsx | 25 +-- src/components/HelpModal/HelpSection.tsx | 4 +- src/components/HelpModal/HintItem.tsx | 2 +- src/components/Loading.tsx | 7 +- src/components/ThemeProvider.tsx | 44 ----- .../WeatherModal/CurrentWeather.tsx | 59 +++--- .../WeatherModal/ExtendedForecast.tsx | 3 +- src/components/WeatherModal/WeatherGraphs.tsx | 31 ++- src/components/WeatherModal/WeatherModal.tsx | 30 +-- src/components/index.ts | 1 + src/hooks/useAPI.ts | 184 +++--------------- src/hooks/useCommonHooks.ts | 18 +- src/pages/homepage/Homepage.tsx | 89 ++------- .../components/Applications/AppItem.tsx | 2 +- .../components/Applications/Applications.tsx | 5 +- .../components/Bookmarks/Bookmarks.tsx | 2 +- .../components/Bookmarks/LinkGroups.tsx | 7 +- .../components/Bookmarks/LinkItem.tsx | 3 +- .../homepage/components/Header/Header.tsx | 19 +- .../components/Header/LargeHeader.tsx | 19 +- .../homepage/components/Search/Search.tsx | 53 ++--- .../homepage/components/SettingsButton.tsx | 8 +- src/pages/settings/Settings.tsx | 5 +- .../components/Applications/Applications.tsx | 9 +- .../components/Bookmarks/Bookmarks.tsx | 9 +- .../Bookmarks/DebouncedGroupSettingsItem.tsx | 8 +- .../Personalization/Personalization.tsx | 5 +- src/styles/Stack.module.css | 3 +- src/styles/Tabs.module.css | 3 + src/styles/ThemeIcon.module.css | 6 + src/styles/index.ts | 2 + 34 files changed, 225 insertions(+), 466 deletions(-) create mode 100644 src/styles/ThemeIcon.module.css diff --git a/package-lock.json b/package-lock.json index 8f9d984..cc78825 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,6 @@ "@tanstack/react-query-devtools": "^5.74.3", "@tanstack/react-query-persist-client": "^5.74.3", "idb-keyval": "^6.2.1", - "jotai": "^2.12.3", "react": "^19.1.0", "react-dom": "^19.1.0", "react-icons": "^5.5.0", @@ -3801,27 +3800,6 @@ "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", "dev": true }, - "node_modules/jotai": { - "version": "2.12.3", - "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.12.3.tgz", - "integrity": "sha512-DpoddSkmPGXMFtdfnoIHfueFeGP643nqYUWC6REjUcME+PG2UkAtYnLbffRDw3OURI9ZUTcRWkRGLsOvxuWMCg==", - "license": "MIT", - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=17.0.0", - "react": ">=17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - } - } - }, "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", diff --git a/package.json b/package.json index c93a407..4b0c54d 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "@tanstack/react-query-devtools": "^5.74.3", "@tanstack/react-query-persist-client": "^5.74.3", "idb-keyval": "^6.2.1", - "jotai": "^2.12.3", "react": "^19.1.0", "react-dom": "^19.1.0", "react-icons": "^5.5.0", diff --git a/src/App.tsx b/src/App.tsx index 36a79a3..5e2d3b1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,11 +13,10 @@ import { createBrowserRouter, createRoutesFromElements, } from 'react-router-dom'; -import { MainLayout, ThemeProvider } from './components'; +import { Loading, MainLayout, ThemeProvider } from './components'; import '@mantine/core/styles.css'; //import Mantine V7 styles needed by MRT import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import './index.css'; -import Loading from 'components/Loading'; import { Homepage, Settings } from 'pages'; import { ToastContainer } from 'react-toastify'; import type { Theme, ThemePlaylist } from 'types'; diff --git a/src/components/HelpModal/HelpModal.tsx b/src/components/HelpModal/HelpModal.tsx index 2e34e8b..90c26a6 100644 --- a/src/components/HelpModal/HelpModal.tsx +++ b/src/components/HelpModal/HelpModal.tsx @@ -1,7 +1,7 @@ import { Divider, Modal, Stack } from '@mantine/core'; import { useCommonHooks } from 'hooks'; import { modalClasses } from 'styles'; -import type { Provider } from 'types'; +import type { Application, Provider } from 'types'; import HelpSection from './HelpSection'; export function HelpModal({ @@ -12,8 +12,16 @@ export function HelpModal({ closeProviderModal, applicationData, providerData, -}: any) { - const { theme, isMobile } = useCommonHooks(); +}: { + setSearchValue: (value: string) => void; + setProviderLabel: (label: string) => void; + setProviderURL: (url: string) => void; + providerModalOpened: boolean; + closeProviderModal: () => void; + applicationData: Application[]; + providerData: Provider[]; +}) { + const { theme, isMobile, modalStyle } = useCommonHooks(); return ( @@ -59,7 +60,7 @@ export function HelpModal({ { + items={applicationData.map((application: Application) => { return { name: application.url, command: `!${application.baseUrl.substring(1).toLowerCase()}`, diff --git a/src/components/HelpModal/HelpSection.tsx b/src/components/HelpModal/HelpSection.tsx index 38497a2..9db17d2 100644 --- a/src/components/HelpModal/HelpSection.tsx +++ b/src/components/HelpModal/HelpSection.tsx @@ -2,7 +2,7 @@ import { Divider, Group } from '@mantine/core'; import { useCommonHooks } from 'hooks'; import HintItem from './HintItem'; -function HelpSection({ name, items }: any) { +function HelpSection({ name, items }: { name: string; items: object[] }) { const { theme } = useCommonHooks(); return ( <> @@ -30,7 +30,7 @@ function HelpSection({ name, items }: any) { }} > {items - ?.sort((a: any, b: any) => (a.command > b.command ? 1 : -1)) + ?.toSorted((a: any, b: any) => (a.command > b.command ? 1 : -1)) .map((item: any) => ( ))} diff --git a/src/components/HelpModal/HintItem.tsx b/src/components/HelpModal/HintItem.tsx index a59f69e..8f3e4e3 100644 --- a/src/components/HelpModal/HintItem.tsx +++ b/src/components/HelpModal/HintItem.tsx @@ -1,6 +1,6 @@ import { Group, Text } from '@mantine/core'; -function HintItem({ command, name }: any) { +function HintItem({ command, name }: { command: string; name: string }) { return ( ); } - -export default Loading; diff --git a/src/components/ThemeProvider.tsx b/src/components/ThemeProvider.tsx index 97a1e67..82e7aac 100644 --- a/src/components/ThemeProvider.tsx +++ b/src/components/ThemeProvider.tsx @@ -15,53 +15,9 @@ export function ThemeProvider({ '--image-background': `linear-gradient(${theme.backgroundColor}33, ${theme.backgroundColor}33), url(/backgrounds/${theme.backgroundImage})`, '--toastify-color-light': `hwb(from ${theme.primaryColor} h w b / 0.25)`, '--toastify-color-dark': `hwb(from ${theme.primaryColor} h w b / 0.25)`, - '--toastify-color-info': '#3498db', - '--toastify-color-success': '#07bc0c', - '--toastify-color-warning': '#f1c40f', - '--toastify-color-error': 'hsl(6, 78%, 57%)', - '--toastify-color-transparent': 'rgba(255, 255, 255, 0.7)', - '--toastify-icon-color-info': 'var(--toastify-color-info)', - '--toastify-icon-color-success': 'var(--toastify-color-success)', - '--toastify-icon-color-warning': 'var(--toastify-color-warning)', - '--toastify-icon-color-error': 'var(--toastify-color-error)', - - '--toastify-container-width': 'fit-content', - '--toastify-toast-width': '320px', - '--toastify-toast-offset': '16px', - '--toastify-toast-top': - 'max(var(--toastify-toast-offset), env(safe-area-inset-top))', - '--toastify-toast-right': - 'max(var(--toastify-toast-offset), env(safe-area-inset-right))', - '--toastify-toast-left': - 'max(var(--toastify-toast-offset), env(safe-area-inset-left))', - '--toastify-toast-bottom': - 'max(var(--toastify-toast-offset), env(safe-area-inset-bottom))', - '--toastify-toast-background': '#fff', - '--toastify-toast-padding': '14px', - '--toastify-toast-min-height': '64px', - '--toastify-toast-max-height': '800px', - '--toastify-toast-bd-radius': '6px', - '--toastify-toast-shadow': '0px 4px 12px rgba(0, 0, 0, 0.1)', - '--toastify-font-family': 'sans-serif', - '--toastify-z-index': '9999', '--toastify-text-color-light': theme.primaryColor, '--toastify-text-color-dark': theme.primaryColor, - - /* Used only for colored theme */ - '--toastify-text-color-info': '#fff', - '--toastify-text-color-success': '#fff', - '--toastify-text-color-warning': '#fff', - '--toastify-text-color-error': '#fff', - - '--toastify-spinner-color': '#616161', - '--toastify-spinner-color-empty-area': '#e0e0e0', '--toastify-color-progress-light': theme.accentColor, - '--toastify-color-progress-dark': '#bb86fc', - '--toastify-color-progress-info': 'var(--toastify-color-info)', - '--toastify-color-progress-success': 'var(--toastify-color-success)', - '--toastify-color-progress-warning': 'var(--toastify-color-warning)', - '--toastify-color-progress-error': 'var(--toastify-color-error)', - /* used to control the opacity of the progress trail */ '--toastify-color-progress-bgo': 0.2, }} > diff --git a/src/components/WeatherModal/CurrentWeather.tsx b/src/components/WeatherModal/CurrentWeather.tsx index 3230491..115979a 100644 --- a/src/components/WeatherModal/CurrentWeather.tsx +++ b/src/components/WeatherModal/CurrentWeather.tsx @@ -4,9 +4,11 @@ import { useSunStateAPI } from 'hooks/useAPI'; import { useEffect, useState } from 'react'; import { FaLongArrowAltDown, FaLongArrowAltUp, FaWind } from 'react-icons/fa'; import { WiBarometer, WiHumidity, WiSunrise, WiSunset } from 'react-icons/wi'; +import { themeIconClasses } from 'styles'; +import type { CurrentWeather as CW } from 'types'; import WeatherIcon from './WeatherIcon'; -export default function CurrentWeather({ current }: any) { +export default function CurrentWeather({ current }: { current: CW }) { const { currentLocation, isMobile } = useCommonHooks(); const { data: sunStateData } = useSunStateAPI({ lat: currentLocation.lat, @@ -34,19 +36,21 @@ export default function CurrentWeather({ current }: any) { {current ? ( - + - + @@ -117,10 +118,8 @@ export default function CurrentWeather({ current }: any) { @@ -139,10 +138,8 @@ export default function CurrentWeather({ current }: any) { @@ -157,10 +154,8 @@ export default function CurrentWeather({ current }: any) { @@ -177,10 +172,8 @@ export default function CurrentWeather({ current }: any) { @@ -205,10 +198,8 @@ export default function CurrentWeather({ current }: any) { @@ -233,10 +224,8 @@ export default function CurrentWeather({ current }: any) { diff --git a/src/components/WeatherModal/ExtendedForecast.tsx b/src/components/WeatherModal/ExtendedForecast.tsx index 2cc48bb..c23c1de 100644 --- a/src/components/WeatherModal/ExtendedForecast.tsx +++ b/src/components/WeatherModal/ExtendedForecast.tsx @@ -19,9 +19,8 @@ export default function ExtendedForecast({ forecast }: any) { paddingBottom: '.5rem', }} > - + { return new Date(data.startTime).toLocaleTimeString('en-US', { hour: 'numeric', @@ -59,7 +56,7 @@ export default function WeatherGraphs({ hourlyForecast }: any) { }} /> @@ -70,8 +67,8 @@ export default function WeatherGraphs({ hourlyForecast }: any) { type='monotone' name='Temperature' dataKey='temperature' - stroke={theme.primaryColor} - dot={{ stroke: theme.primaryColor, strokeWidth: 1 }} + stroke={'var(--color-text-pri)'} + dot={{ stroke: 'var(--color-text-pri)', strokeWidth: 1 }} /> { return (data.dewpoint.value * 9) / 5 + 32; }} - stroke={theme.accentColor} - dot={{ stroke: theme.primaryColor, strokeWidth: 1 }} + stroke={'var(--color-text-acc)'} + dot={{ stroke: 'var(--color-text-pri)', strokeWidth: 1 }} /> @@ -90,7 +87,7 @@ export default function WeatherGraphs({ hourlyForecast }: any) { { return new Date(data.startTime).toLocaleTimeString('en-US', { hour: 'numeric', @@ -98,7 +95,7 @@ export default function WeatherGraphs({ hourlyForecast }: any) { }); }} /> - + } /> { return data.probabilityOfPrecipitation.value; }} - stroke={theme.primaryColor} - fill={theme.primaryColor} - dot={{ stroke: theme.primaryColor, strokeWidth: 1 }} + stroke={'var(--color-text-pri)'} + fill={'var(--color-text-pri)'} + dot={{ stroke: 'var(--color-text-pri)', strokeWidth: 1 }} /> { return data.relativeHumidity.value; }} - stroke={theme.accentColor} - fill={theme.accentColor} - dot={{ stroke: theme.primaryColor, strokeWidth: 1 }} + stroke={'var(--color-text-acc)'} + fill={'var(--color-text-acc)'} + dot={{ stroke: 'var(--color-text-pri)', strokeWidth: 1 }} /> diff --git a/src/components/WeatherModal/WeatherModal.tsx b/src/components/WeatherModal/WeatherModal.tsx index 5e93288..67f516b 100644 --- a/src/components/WeatherModal/WeatherModal.tsx +++ b/src/components/WeatherModal/WeatherModal.tsx @@ -4,20 +4,29 @@ import { modalClasses } from 'styles'; import { useCommonHooks, useWeatherAPI } from 'hooks'; import { useEffect, useState } from 'react'; +import type { CurrentWeather as CW, ExtendedForecast as EF } from 'types'; import CurrentWeather from './CurrentWeather'; import ExtendedForecast from './ExtendedForecast'; import WeatherGraphs from './WeatherGraphs'; export function WeatherModal({ + current, + extendedForecast, + weatherModalOpened, setSearchValue, setProviderLabel, setProviderURL, - current, - extendedForecast, closeWeatherModal, - weatherModalOpened, -}: any) { - const { theme, isMobile, currentUser, currentLocation } = useCommonHooks(); +}: { + current: CW; + extendedForecast: EF[]; + weatherModalOpened: boolean; + setSearchValue: (value: string) => void; + setProviderLabel: (label: string) => void; + setProviderURL: (url: string) => void; + closeWeatherModal: () => void; +}) { + const { isMobile, currentUser, currentLocation, modalStyle } = useCommonHooks(); const { data: hourly } = useWeatherAPI({ endpoint: 'hourly', office: currentLocation.office, @@ -46,14 +55,7 @@ export function WeatherModal({ setProviderURL('https://www.google.com/search?q='); closeWeatherModal(); }} - style={{ - '--color-text-pri': theme.primaryColor, - '--color-text-acc': theme.accentColor, - '--color-background': theme.backgroundColor, - '--color-border': theme.borderColor, - '--color-search-background': theme.searchColor, - '--image-background': `url(${theme.backgroundImage})`, - }} + style={modalStyle} closeOnEscape closeOnClickOutside > @@ -61,7 +63,7 @@ export function WeatherModal({ - + Weather Forecast for{' '} diff --git a/src/components/index.ts b/src/components/index.ts index d1c9930..4afbd2e 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -2,3 +2,4 @@ export { HelpModal } from './HelpModal/HelpModal'; export { WeatherModal } from './WeatherModal/WeatherModal'; export { ThemeProvider } from './ThemeProvider'; export { MainLayout } from './MainLayout'; +export { Loading } from './Loading'; diff --git a/src/hooks/useAPI.ts b/src/hooks/useAPI.ts index 609b854..45bf56b 100644 --- a/src/hooks/useAPI.ts +++ b/src/hooks/useAPI.ts @@ -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 => { - // 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, - }; -} diff --git a/src/hooks/useCommonHooks.ts b/src/hooks/useCommonHooks.ts index 2e63387..f88f86c 100644 --- a/src/hooks/useCommonHooks.ts +++ b/src/hooks/useCommonHooks.ts @@ -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({ + const [themeName, setThemeName] = useLocalStorage({ 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({ + const [currentTheme, setCurrentTheme] = useLocalStorage({ key: 'theme', defaultValue: 'blackboard', }); - const [currentThemePlaylist, setCurrentThemePlaylist] = useLocalStorage({ + const [currentThemePlaylist, setCurrentThemePlaylist] = useLocalStorage({ 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, diff --git a/src/pages/homepage/Homepage.tsx b/src/pages/homepage/Homepage.tsx index ba2b090..50ef908 100644 --- a/src/pages/homepage/Homepage.tsx +++ b/src/pages/homepage/Homepage.tsx @@ -8,7 +8,6 @@ import type { CurrentWeather as CW, ExtendedForecast as EF, Message, - OutlookWeather, PersonalData, } from 'types'; import Applications from './components/Applications/Applications'; @@ -85,31 +84,31 @@ export function Homepage() { const { data: weatherMetadata } = useOSMAPI({ address: personal?.[0].physicalAddress }); const { data: points } = useWeatherAPI({ endpoint: 'points', - lat: weatherMetadata?.[0].lat || currentLocation.lat, - lon: weatherMetadata?.[0].lon || currentLocation.lon, + lat: weatherMetadata?.[0].lat ?? currentLocation.lat, + lon: weatherMetadata?.[0].lon ?? currentLocation.lon, }); const { data: basicWeather } = useWeatherAPI({ endpoint: 'baseline', - office: points?.properties.gridId || currentLocation.office, - x: points?.properties.gridX || currentLocation.x, - y: points?.properties.gridY || currentLocation.y, + office: points?.properties.gridId ?? currentLocation.office, + x: points?.properties.gridX ?? currentLocation.x, + y: points?.properties.gridY ?? currentLocation.y, }); const { data: stations } = useWeatherAPI({ endpoint: 'stations', - office: points?.properties.gridId || currentLocation.office, - x: points?.properties.gridX || currentLocation.x, - y: points?.properties.gridY || currentLocation.y, + office: points?.properties.gridId ?? currentLocation.office, + x: points?.properties.gridX ?? currentLocation.x, + y: points?.properties.gridY ?? currentLocation.y, }); const { data: weather } = useWeatherAPI({ endpoint: 'current', station: - stations?.features[0].properties.stationIdentifier || currentLocation.station, + stations?.features[0].properties.stationIdentifier ?? currentLocation.station, }); const { data: forecast } = useWeatherAPI({ endpoint: 'forecast', - office: points?.properties.gridId || currentLocation.office, - x: points?.properties.gridX || currentLocation.x, - y: points?.properties.gridY || currentLocation.y, + office: points?.properties.gridId ?? currentLocation.office, + x: points?.properties.gridX ?? currentLocation.x, + y: points?.properties.gridY ?? currentLocation.y, }); const [current, setCurrent] = useState({ icon: '', @@ -125,17 +124,7 @@ export function Homepage() { windDirection: 0, pressure: 0, }); - const [outlook, setOutlook] = useState([ - { - name: '', - icon: '', - isDayTime: true, - temperature: 0, - precipitation: 0, - windSpeed: '0 mph', - windDirection: 'N', - }, - ]); + const [extendedForecast, setExtendedForecast] = useState([ { detailedForecast: '', @@ -150,7 +139,7 @@ export function Homepage() { useEffect(() => { if (weather !== undefined && basicWeather !== undefined) { setCurrent({ - icon: weather.features[0]?.properties.icon?.replace('medium', 'large') || '', + icon: weather.features[0]?.properties.icon?.replace('medium', 'large') ?? '', textDescription: weather.features[0]?.properties.textDescription, temperature: Math.round( (weather.features[0]?.properties.temperature.value * 9) / 5 + 32, @@ -162,7 +151,7 @@ export function Homepage() { (basicWeather.properties.minTemperature.values[0].value * 9) / 5 + 32, ), precipitation: - weather.features[0]?.properties.precipitationLastHour.value || 'N/A', + weather.features[0]?.properties.precipitationLastHour.value ?? 'N/A', humidity: Math.round(weather.features[0]?.properties.relativeHumidity.value), heatIndex: basicWeather.properties.heatIndex?.values[0].value === null @@ -186,30 +175,6 @@ export function Homepage() { }, [weather, basicWeather]); useEffect(() => { if (forecast) { - setOutlook([ - { - name: forecast.properties.periods[0].name, - icon: forecast.properties.periods[0].icon.replace('medium', 'large'), - isDayTime: forecast.properties.periods[0].isDaytime, - temperature: forecast.properties.periods[0].temperature, - precipitation: Math.round( - forecast.properties.periods[0].probabilityOfPrecipitation.value, - ), - windSpeed: forecast.properties.periods[0].windSpeed, - windDirection: forecast.properties.periods[0].windDirection, - }, - { - name: forecast.properties.periods[1].name, - icon: forecast.properties.periods[1].icon.replace('medium', 'large'), - isDayTime: forecast.properties.periods[1].isDaytime, - temperature: forecast.properties.periods[1].temperature, - precipitation: Math.round( - forecast.properties.periods[1].probabilityOfPrecipitation.value, - ), - windSpeed: forecast.properties.periods[1].windSpeed, - windDirection: forecast.properties.periods[1].windDirection, - }, - ]); setExtendedForecast( forecast.properties.periods.map((item: any) => ({ detailedForecast: item.detailedForecast, @@ -241,25 +206,18 @@ export function Homepage() { <>
@@ -269,28 +227,19 @@ export function Homepage() { <> - + { + .map((application: Application) => { return ( ); })} diff --git a/src/pages/homepage/components/Bookmarks/Bookmarks.tsx b/src/pages/homepage/components/Bookmarks/Bookmarks.tsx index 256ef55..2d27b9f 100644 --- a/src/pages/homepage/components/Bookmarks/Bookmarks.tsx +++ b/src/pages/homepage/components/Bookmarks/Bookmarks.tsx @@ -5,7 +5,7 @@ import { textClasses } from 'styles'; import type { Bookmark, BookmarkGroup } from 'types'; import LinkGroups from './LinkGroups'; -function Links({ contentType }: any) { +function Links({ contentType }: { contentType: [string] }) { const { data: linkData, isFetched: linksIsFetched } = useQueryAPI({ endpoint: 'api/links', }); diff --git a/src/pages/homepage/components/Bookmarks/LinkGroups.tsx b/src/pages/homepage/components/Bookmarks/LinkGroups.tsx index 6b2789b..818b5ca 100644 --- a/src/pages/homepage/components/Bookmarks/LinkGroups.tsx +++ b/src/pages/homepage/components/Bookmarks/LinkGroups.tsx @@ -1,8 +1,9 @@ import { Stack, Text } from '@mantine/core'; import { textClasses } from 'styles'; +import type { Bookmark } from 'types'; import LinkItem from './LinkItem'; -function LinkGroups({ links, category }: any) { +function LinkGroups({ links, category }: { links: Bookmark[]; category: string }) { return ( {links - ?.sort((a: any, b: any) => (a.index > b.index ? 1 : -1)) - .map((link: any) => ( + ?.toSorted((a: Bookmark, b: Bookmark) => (a.index > b.index ? 1 : -1)) + .map((link: Bookmark) => ( ))} diff --git a/src/pages/homepage/components/Bookmarks/LinkItem.tsx b/src/pages/homepage/components/Bookmarks/LinkItem.tsx index 72938a6..e2dbc8c 100644 --- a/src/pages/homepage/components/Bookmarks/LinkItem.tsx +++ b/src/pages/homepage/components/Bookmarks/LinkItem.tsx @@ -1,7 +1,8 @@ import { Text } from '@mantine/core'; import { textClasses } from 'styles'; +import type { Bookmark } from 'types'; -function LinkItem({ link }: any) { +function LinkItem({ link }: { link: Bookmark }) { return ( void; + weather: any; + basicWeather: any; + openWeatherModal: () => void; +}) { const [time, setTime] = useState(new Date()); const { isMobile, currentUser, fixDigits } = useCommonHooks(); @@ -25,7 +34,7 @@ function Header({ useEffect(() => { if (weather !== undefined && basicWeather !== undefined) { setCurrent({ - icon: weather.features[0]?.properties.icon?.replace('medium', 'large') || '', + icon: weather.features[0]?.properties.icon?.replace('medium', 'large') ?? '', textDescription: weather.features[0]?.properties.textDescription, temperature: Math.round( (weather.features[0]?.properties.temperature.value * 9) / 5 + 32, @@ -37,7 +46,7 @@ function Header({ (basicWeather.properties.minTemperature.values[0].value * 9) / 5 + 32, ), precipitation: - weather.features[0]?.properties.precipitationLastHour.value || 'N/A', + weather.features[0]?.properties.precipitationLastHour.value ?? 'N/A', humidity: Math.round(weather.features[0]?.properties.relativeHumidity.value), heatIndex: basicWeather.properties.heatIndex?.values[0].value === null @@ -128,17 +137,17 @@ function Header({ > {current.temperature}°F {current.highTemp}°F/{current.lowTemp}°F diff --git a/src/pages/homepage/components/Header/LargeHeader.tsx b/src/pages/homepage/components/Header/LargeHeader.tsx index b014df7..191389d 100644 --- a/src/pages/homepage/components/Header/LargeHeader.tsx +++ b/src/pages/homepage/components/Header/LargeHeader.tsx @@ -2,8 +2,21 @@ import { Stack, Text } from '@mantine/core'; import { useCommonHooks } from 'hooks'; import { useEffect, useState } from 'react'; import { textClasses } from 'styles'; +import type { CurrentWeather as CW } from 'types'; -function LargeHeader({ greet, date, setCurrent, weather, basicWeather }: any) { +function LargeHeader({ + greet, + date, + setCurrent, + weather, + basicWeather, +}: { + greet: string; + date: string; + weather: any; + basicWeather: any; + setCurrent: (current: CW) => void; +}) { const [time, setTime] = useState(new Date()); const { isMobile, currentUser, fixDigits } = useCommonHooks(); @@ -17,7 +30,7 @@ function LargeHeader({ greet, date, setCurrent, weather, basicWeather }: any) { useEffect(() => { if (weather !== undefined && basicWeather !== undefined) { setCurrent({ - icon: weather.features[0]?.properties.icon?.replace('medium', 'large') || '', + icon: weather.features[0]?.properties.icon?.replace('medium', 'large') ?? '', textDescription: weather.features[0]?.properties.textDescription, temperature: Math.round( (weather.features[0]?.properties.temperature.value * 9) / 5 + 32, @@ -29,7 +42,7 @@ function LargeHeader({ greet, date, setCurrent, weather, basicWeather }: any) { (basicWeather.properties.minTemperature.values[0].value * 9) / 5 + 32, ), precipitation: - weather.features[0]?.properties.precipitationLastHour.value || 'N/A', + weather.features[0]?.properties.precipitationLastHour.value ?? 'N/A', humidity: Math.round(weather.features[0]?.properties.relativeHumidity.value), heatIndex: basicWeather.properties.heatIndex?.values[0].value === null diff --git a/src/pages/homepage/components/Search/Search.tsx b/src/pages/homepage/components/Search/Search.tsx index d0359b6..16df514 100644 --- a/src/pages/homepage/components/Search/Search.tsx +++ b/src/pages/homepage/components/Search/Search.tsx @@ -12,16 +12,21 @@ import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useEffect, useState } from 'react'; import { FiEdit, FiTrash } from 'react-icons/fi'; import { actionIconClasses, selectClasses } from 'styles'; -import type { Application, Provider } from 'types'; +import type { Application, CurrentWeather, ExtendedForecast, Provider } from 'types'; export default function Search({ current, - outlook, extendedForecast, weatherModalOpened, closeWeatherModal, openWeatherModal, -}: any) { +}: { + current: CurrentWeather; + extendedForecast: ExtendedForecast[]; + weatherModalOpened: boolean; + closeWeatherModal: () => void; + openWeatherModal: () => void; +}) { const { data: providers, isFetched: providersIsFetched } = useQueryAPI({ endpoint: 'api/providers', }); @@ -29,14 +34,13 @@ export default function Search({ const { data: apps, isFetching: isAppsFetching } = useQueryAPI({ endpoint: 'api/apps', }); - const [applicationData, setApplicationData] = useState([]); + const [applicationData, setApplicationData] = useState([]); const { data: history, isFetched: historyIsFetched } = useQueryAPI({ endpoint: 'api/searchHistory', }); const [searchHistory, setSearchHistory] = useState([]); const [filteredHistory, setFilteredHistory] = useState([]); - const { mutateAsync: createHistoryItem } = useMutationAPI(); - const { mutateAsync: deleteHistoryItem } = useMutationAPI(); + const { mutateAsync } = useMutationAPI(); const [providerModalOpened, { open: openProviderModal, close: closeProviderModal }] = useDisclosure(false); const [dropdownOpened, { open: openDropdown, close: closeDropdown }] = useDisclosure(); @@ -59,7 +63,7 @@ export default function Search({ const handleSearchHistoryItem = (item: any) => { const url = - providers.find((provider: any) => provider.prefix === item.option.prefix)?.url || + providers.find((provider: any) => provider.prefix === item.option.prefix)?.url ?? 'https://www.google.com/search?q='; open(`${url}${item.option.searchTerm}`, '_self'); }; @@ -73,7 +77,7 @@ export default function Search({ let subtext = ''; if (item.target.value.startsWith('/')) { option = - item.target.value.substr(1, item.target.value.indexOf(' ') - 1) || + item.target.value.substr(1, item.target.value.indexOf(' ') - 1) ?? item.target.value.substr(1); subtext = item.target.value.substr(2 + option.length); } else if (item.target.value.startsWith('!')) { @@ -87,7 +91,7 @@ export default function Search({ if (providerLabel === 'Show help') { openProviderModal(); } else if (item.target.value.startsWith('/')) { - createHistoryItem({ + mutateAsync({ method: 'POST', endpoint: 'api/searchHistory', body: { prefix: providerPrefix, provider: providerLabel, searchTerm: subtext }, @@ -100,16 +104,18 @@ export default function Search({ const url = applicationData.find( (app: Application) => app.baseUrl.substring(1).toLowerCase() === item.target.value.substr(1), - ).url; + )?.url; const validUrl = - url.startsWith('http://') || url.startsWith('https://') ? url : `http://${url}`; + url?.startsWith('http://') || url?.startsWith('https://') + ? url + : `http://${url}`; open(validUrl, '_self'); } else if (urlPattern.test(item.target.value)) { if (/^(https?:\/\/).*/i.test(item.target.value)) window.location = item.target.value; else open(`https://${item.target.value}`, '_self'); } else { - createHistoryItem({ + mutateAsync({ method: 'POST', endpoint: 'api/searchHistory', body: { prefix: providerPrefix, provider: providerLabel, searchTerm: subtext }, @@ -220,8 +226,8 @@ export default function Search({ setProviderLabel('Show weather'); } else if ( applicationData?.find( - (app: any) => - app.baseUrl.substr(1).toLowerCase() === item.target.value.substr(1), + (app: Application) => + app.baseUrl.substring(1).toLowerCase() === item.target.value.substr(1), ) ) { setProviderLabel(`Launch ${item.target.value.substr(1)}`); @@ -239,10 +245,10 @@ export default function Search({ const splittedSearch = search.toLowerCase().trim().split(' '); if (splittedSearch[0].startsWith('!')) { return apps - .filter((app: any) => + .filter((app: Application) => app.name.toLowerCase().includes(splittedSearch[0].substring(1).toLowerCase()), ) - .map((app: any) => { + .map((app: Application) => { return { label: app.name, value: app.baseUrl, @@ -257,7 +263,7 @@ export default function Search({ .toLowerCase() .trim() .split(' ') - .some((word: any) => word.includes(searchWord)), + .some((word: string) => word.includes(searchWord)), ); }) .slice(0, 10); @@ -327,7 +333,7 @@ export default function Search({ classNames={actionIconClasses} onClick={(e) => { e.stopPropagation(); - deleteHistoryItem({ + mutateAsync({ method: 'DELETE', endpoint: 'api/searchHistory', body: { id: item.option.id }, @@ -417,7 +423,7 @@ export default function Search({ const option = `${item.prefix} ${item.searchTerm}`; const words = option.toLowerCase().trim().split(' '); return splittedSearch.every((searchWord) => - words.some((word: any) => word.includes(searchWord)), + words.some((word: string) => word.includes(searchWord)), ); }) .slice(0, 10); @@ -482,20 +488,19 @@ export default function Search({ setSearchValue={setSearchValue} setProviderLabel={setProviderLabel} setProviderURL={setProviderURL} - providerModalOpened={providerModalOpened} applicationData={applicationData} providerData={providerData} + providerModalOpened={providerModalOpened} closeProviderModal={closeProviderModal} /> ); diff --git a/src/pages/homepage/components/SettingsButton.tsx b/src/pages/homepage/components/SettingsButton.tsx index 18cb7d7..a04257a 100644 --- a/src/pages/homepage/components/SettingsButton.tsx +++ b/src/pages/homepage/components/SettingsButton.tsx @@ -38,7 +38,7 @@ function SettingsButton() { const { data: themePlaylists } = useQueryAPI({ endpoint: 'api/themePlaylists', }); - const { mutateAsync: wakeRequest } = useMutationAPI(); + const { mutateAsync } = useMutationAPI(); const [allThemeData, setAllThemeData] = useState([]); const [categories, setCategories] = useState([{ value: 'All', label: 'All' }]); const [selectedClient, setSelectedClient] = useState(wolClients?.[0]); @@ -64,7 +64,7 @@ function SettingsButton() { setCurrentThemePlaylist, } = useCommonHooks(); const handleWakeRequest = () => { - wakeRequest({ + mutateAsync({ method: 'POST', endpoint: 'api/wakeClient', body: selectedClient || wolClients?.[0], @@ -151,7 +151,7 @@ function SettingsButton() { defaultValue={wolClientsData?.[0]?.hostname} data={ wolClientsData - ?.map((wolClient: any) => { + ?.map((wolClient: WolClient) => { return { value: wolClient.hostname, label: `${wolClient.hostname} (${wolClient.macAddress})`, @@ -241,7 +241,7 @@ function SettingsButton() { label: playlist.name, }; }) - .toSorted((a: any, b: any) => a.label.localeCompare(b.label)) || [] + .toSorted((a: any, b: any) => a.label.localeCompare(b.label)) ?? [] } w='92%' classNames={{ diff --git a/src/pages/settings/Settings.tsx b/src/pages/settings/Settings.tsx index de0d709..9d0ecd7 100644 --- a/src/pages/settings/Settings.tsx +++ b/src/pages/settings/Settings.tsx @@ -27,7 +27,7 @@ export function Settings() { const { tabValue } = useParams(); return ( - + navigate(`/${import.meta.env.VITE_BASE_URL}/settings/${value}`) } - style={{ - overflow: 'hidden', - }} > diff --git a/src/pages/settings/components/Applications/Applications.tsx b/src/pages/settings/components/Applications/Applications.tsx index 147fff2..5d66747 100644 --- a/src/pages/settings/components/Applications/Applications.tsx +++ b/src/pages/settings/components/Applications/Applications.tsx @@ -45,7 +45,7 @@ const ApplicationSettings = () => { }); const [applicationData, setApplicationData] = useState([]); const [applicationList, setApplicationList] = useState([]); - const { theme, isMobile } = useCommonHooks(); + const { theme, isMobile, reorder } = useCommonHooks(); const [newApplication, setNewApplication] = useState(defaultApplication); const { mutateAsync } = useMutationAPI(); const [searchValue, setSearchValue] = useState(''); @@ -53,13 +53,6 @@ const ApplicationSettings = () => { const [dragging, setDragging] = useState(false); const [isOpen, setIsOpen] = useState(false); - 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; - }; - function onDragEnd(result: any) { if (!result.destination) { return; diff --git a/src/pages/settings/components/Bookmarks/Bookmarks.tsx b/src/pages/settings/components/Bookmarks/Bookmarks.tsx index aee8b16..8393a29 100644 --- a/src/pages/settings/components/Bookmarks/Bookmarks.tsx +++ b/src/pages/settings/components/Bookmarks/Bookmarks.tsx @@ -28,19 +28,12 @@ const BookmarkSettings = () => { const [groups, setGroups] = useState([]); const [newGroup, setNewGroup] = useState(defaultGroup); const [groupList, setGroupList] = useState([]); - const { theme, isMobile } = useCommonHooks(); + const { theme, isMobile, reorder } = useCommonHooks(); const { mutateAsync } = useMutationAPI(); const [searchValue, setSearchValue] = useState(''); const [debouncedGroupSearch] = useDebouncedValue(searchValue, 500); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); - 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 handleClose = () => { setNewGroup(defaultGroup); closeModal(); diff --git a/src/pages/settings/components/Bookmarks/DebouncedGroupSettingsItem.tsx b/src/pages/settings/components/Bookmarks/DebouncedGroupSettingsItem.tsx index 0af8f64..eff3a0e 100644 --- a/src/pages/settings/components/Bookmarks/DebouncedGroupSettingsItem.tsx +++ b/src/pages/settings/components/Bookmarks/DebouncedGroupSettingsItem.tsx @@ -27,7 +27,7 @@ function DebouncedGroupSettingsItem({ bookmarks, searchTerm, }: { bookmarkGroup: BookmarkGroup; bookmarks: Bookmark[]; searchTerm: string }) { - const { theme, isMobile, handleDelete } = useCommonHooks(); + const { theme, isMobile, handleDelete, reorder } = useCommonHooks(); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [popoverOpened, setPopoverOpened] = useState(false); const { mutateAsync } = useMutationAPI(); @@ -44,12 +44,6 @@ function DebouncedGroupSettingsItem({ const [bookmarkList, setBookmarkList] = useState(bookmarks); const [isOpen, setIsOpen] = useState(false); - 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; - }; function onDragEnd(result: any) { if (!result.destination) { return; diff --git a/src/pages/settings/components/Personalization/Personalization.tsx b/src/pages/settings/components/Personalization/Personalization.tsx index 6357530..d4a69bc 100644 --- a/src/pages/settings/components/Personalization/Personalization.tsx +++ b/src/pages/settings/components/Personalization/Personalization.tsx @@ -62,7 +62,10 @@ const PersonalizationSettings = () => { setSearchValue={() => {}} searchPlaceholder={undefined} /> - + {personalIsFetching && (