diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 83bc434..f397aac 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,2 +1,2 @@ -export { useQueryAPI, useWeatherAPI, useMutationAPI, useCensusAPI } from './useAPI'; +export { useQueryAPI, useWeatherAPI, useMutationAPI, useOSMAPI } from './useAPI'; export { useCommonHooks } from './useCommonHooks'; diff --git a/src/hooks/useAPI.ts b/src/hooks/useAPI.ts index f35eaad..06b1ce6 100644 --- a/src/hooks/useAPI.ts +++ b/src/hooks/useAPI.ts @@ -1,6 +1,6 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { useCommonHooks } from 'hooks'; -import { useCallback, useState } from 'react'; +import { useCallback, 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}`; @@ -33,8 +33,10 @@ export function useQueryAPI({ endpoint }: { endpoint: string }) { } export function useMutationAPI() { - const { notify } = useCommonHooks(); - const [message, setMessage] = useState(''); + const toastId = useRef(null); + const notify = () => { + toastId.current = toast('Sending...', { position: 'bottom-right', autoClose: false }); + }; const callEndpoint = async ( method: string, endpoint: string, @@ -53,11 +55,9 @@ export function useMutationAPI() { }, body: JSON.stringify(body), }; - setMessage('Sending...'); - notify(message); + notify(); const response = await fetch(`${url}/${endpoint}`, options); if (!response.ok) { - notify('Error!'); const responseData = await response.json(); if (typeof responseData.detail !== 'string') { const errorResponse = JSON.stringify({ @@ -69,7 +69,11 @@ export function useMutationAPI() { loc: responseData.detail[0]?.loc, type: responseData.detail[0]?.type, }); - + toast.update(toastId.current, { + render: errorResponse, + type: 'error', + autoClose: 5000, + }); throw new Error(errorResponse); } if (typeof responseData.detail === 'object') { @@ -80,7 +84,11 @@ export function useMutationAPI() { endpoint: endpoint, msg: responseData.detail.message, }); - + toast.update(toastId.current, { + render: errorResponse, + type: 'error', + autoClose: 5000, + }); throw new Error(errorResponse); } const errorResponse = JSON.stringify({ @@ -90,10 +98,18 @@ export function useMutationAPI() { endpoint: endpoint, msg: responseData.detail, }); - + toast.update(toastId.current, { + render: errorResponse, + type: 'error', + autoClose: 5000, + }); throw new Error(errorResponse); } - setMessage('Success!'); + toast.update(toastId.current, { + render: 'Success!', + autoClose: 2000, + transition: Flip, + }); return await response.json(); }; @@ -117,16 +133,36 @@ export function useMutationAPI() { }); } -export function useWeatherAPI({ endpoint }: { endpoint: string }) { +export function useWeatherAPI({ + endpoint, + lat, + lon, + office, + station, + x, + y, +}: { + endpoint: string; + lat?: number; + lon?: number; + office?: string; + station?: string; + x?: number; + y?: number; +}) { let url = ''; if (endpoint === 'baseline') { - url = 'https://api.weather.gov/gridpoints/HGX/55,96'; + url = `https://api.weather.gov/gridpoints/${office}/${x},${y}`; } else if (endpoint === 'current') { - url = 'https://api.weather.gov/stations/KSGR/observations'; + url = `https://api.weather.gov/stations/${station}/observations`; } else if (endpoint === 'forecast') { - url = 'https://api.weather.gov/gridpoints/HGX/55,96/forecast'; + url = `https://api.weather.gov/gridpoints/${office}/${x},${y}/forecast`; } else if (endpoint === 'hourly') { - url = 'https://api.weather.gov/gridpoints/HGX/55,96/forecast/hourly'; + url = `https://api.weather.gov/gridpoints/${office}/${x},${y}/forecast/hourly`; + } else if (endpoint === 'points') { + url = `https://api.weather.gov/points/${lat},${lon}`; + } else if (endpoint === 'stations') { + url = `https://api.weather.gov/gridpoints/${office}/${x},${y}/stations`; } const callEndpoint = async () => { const response = await fetch(url); @@ -142,15 +178,15 @@ export function useWeatherAPI({ endpoint }: { endpoint: string }) { }); } -export function useCensusAPI({ address }: { address: string }) { - const url = `https://geocoding.geo.census.gov/geocoder/locations/onelineaddress?address=${address?.replaceAll(' ', '+').replaceAll(',', '%2C').replaceAll('#', '%23')}&benchmark=4&format=json`; - +export function useOSMAPI({ address }: { address: string }) { + const url = `https://nominatim.openstreetmap.org/search?addressdetails=1&format=jsonv2&limit=1&q=${address?.replaceAll(' ', '+')}`; const callEndpoint = async () => { const response = await fetch(url); if (!response.ok) { throw new Error(`${response.status} ${response.statusText}`); } - return await response.json(); + const data = await response.json(); + return await data; }; return useQuery({ diff --git a/src/hooks/useCommonHooks.ts b/src/hooks/useCommonHooks.ts index 322679a..c67ea45 100644 --- a/src/hooks/useCommonHooks.ts +++ b/src/hooks/useCommonHooks.ts @@ -2,8 +2,7 @@ import { useLocalStorage } from '@mantine/hooks'; import { appTheme } from 'contexts'; import { useAtom } from 'jotai'; import { useEffect, useState } from 'react'; -import { toast } from 'react-toastify'; -import { useCensusAPI, useQueryAPI, useWeatherAPI } from './useAPI'; +import { useOSMAPI, useQueryAPI, useWeatherAPI } from './useAPI'; export function useCommonHooks() { const isMobile = window.matchMedia('(max-width: 600px)').matches; @@ -62,10 +61,7 @@ export function useCommonHooks() { isFetching: wolClientsIsFetching, isLoading: wolClientsIsLoading, } = useQueryAPI({ endpoint: 'api/wolClients' }); - const { data: basicWeather } = useWeatherAPI({ endpoint: 'baseline' }); - const { data: weather } = useWeatherAPI({ endpoint: 'current' }); - const { data: forecast } = useWeatherAPI({ endpoint: 'forecast' }); - const { data: hourly } = useWeatherAPI({ endpoint: 'hourly' }); + const [theme, setTheme] = useAtom(appTheme); const [themeName, setThemeName] = useLocalStorage({ key: 'theme', @@ -102,8 +98,6 @@ export function useCommonHooks() { const [applicationData, setApplicationData] = useState([]); - const notify = (notification: any) => toast(notification, { position: 'bottom-right' }); - useEffect(() => { if (apps && !isAppsFetching) { setApplicationData(apps); @@ -161,17 +155,40 @@ export function useCommonHooks() { } }, [wolClients, wolClientsIsFetched]); - const { data: locationData } = useCensusAPI({ - address: personalData?.[0]?.physicalAddress, + const { data: weatherMetadata } = useOSMAPI({ address: personal?.[0].physicalAddress }); + const { data: points } = useWeatherAPI({ + endpoint: 'points', + lat: weatherMetadata?.[0].lat, + lon: weatherMetadata?.[0].lon, + }); + const { data: basicWeather } = useWeatherAPI({ + endpoint: 'baseline', + office: points?.properties.gridId, + x: points?.properties.gridX, + y: points?.properties.gridY, + }); + const { data: stations } = useWeatherAPI({ + endpoint: 'stations', + office: points?.properties.gridId, + x: points?.properties.gridX, + y: points?.properties.gridY, + }); + const { data: weather } = useWeatherAPI({ + endpoint: 'current', + station: stations?.features[0].properties.stationIdentifier, + }); + const { data: forecast } = useWeatherAPI({ + endpoint: 'forecast', + office: points?.properties.gridId, + x: points?.properties.gridX, + y: points?.properties.gridY, + }); + const { data: hourly } = useWeatherAPI({ + endpoint: 'hourly', + office: points?.properties.gridId, + x: points?.properties.gridX, + y: points?.properties.gridY, }); - - const [location, setLocation] = useState([]); - - useEffect(() => { - if (locationData) { - setLocation(locationData); - } - }, [locationData]); return { isMobile, @@ -234,7 +251,5 @@ export function useCommonHooks() { wolClientsIsFetched, wolClientsIsFetching, wolClientsIsLoading, - location, - notify, }; } diff --git a/src/pages/homepage/Homepage.tsx b/src/pages/homepage/Homepage.tsx index eeb1ee0..60c5a95 100644 --- a/src/pages/homepage/Homepage.tsx +++ b/src/pages/homepage/Homepage.tsx @@ -79,11 +79,11 @@ export function Homepage() { }, [messages, isMessagesFetching]); useEffect(() => { - if (weather && basicWeather) { + if (weather !== undefined && basicWeather !== undefined) { setCurrent({ - icon: weather.features[0].properties.icon?.replace('medium', 'large') || '', + icon: weather.features[0]?.properties.icon?.replace('medium', 'large') || '', temperature: Math.round( - (weather.features[0].properties.temperature.value * 9) / 5 + 32, + (weather.features[0]?.properties.temperature.value * 9) / 5 + 32, ), highTemp: Math.round( (basicWeather.properties.maxTemperature.values[0].value * 9) / 5 + 32, @@ -92,15 +92,15 @@ export function Homepage() { (basicWeather.properties.minTemperature.values[0].value * 9) / 5 + 32, ), precipitation: - weather.features[0].properties.precipitationLastHour.value || 'N/A', - humidity: Math.round(weather.features[0].properties.relativeHumidity.value), + weather.features[0]?.properties.precipitationLastHour.value || 'N/A', + humidity: Math.round(weather.features[0]?.properties.relativeHumidity.value), heatIndex: Math.round( - weather.features[0].properties.heatIndex.value !== null - ? (weather.features[0].properties.heatIndex.value * 9) / 5 + 32 - : (weather.features[0].properties.temperature.value * 9) / 5 + 32, + weather.features[0]?.properties.heatIndex.value !== null + ? (weather.features[0]?.properties.heatIndex.value * 9) / 5 + 32 + : (weather.features[0]?.properties.temperature.value * 9) / 5 + 32, ), - windSpeed: Math.round(weather.features[0].properties.windSpeed.value / 1.60934), - windDirection: weather.features[0].properties.windDirection.value, + windSpeed: Math.round(weather.features[0]?.properties.windSpeed.value / 1.60934), + windDirection: weather.features[0]?.properties.windDirection.value, }); } }, [weather, basicWeather]); diff --git a/src/pages/homepage/components/Header/Header.tsx b/src/pages/homepage/components/Header/Header.tsx index 4e5e96b..911d978 100644 --- a/src/pages/homepage/components/Header/Header.tsx +++ b/src/pages/homepage/components/Header/Header.tsx @@ -31,11 +31,11 @@ function Header({ }, []); useEffect(() => { - if (weather && basicWeather) { + if (weather !== undefined && basicWeather !== undefined) { setCurrent({ - icon: weather.features[0].properties.icon?.replace('medium', 'large') || '', + icon: weather.features[0]?.properties.icon?.replace('medium', 'large') || '', temperature: Math.round( - (weather.features[0].properties.temperature.value * 9) / 5 + 32, + (weather.features[0]?.properties.temperature.value * 9) / 5 + 32, ), highTemp: Math.round( (basicWeather.properties.maxTemperature.values[0].value * 9) / 5 + 32, @@ -44,13 +44,13 @@ function Header({ (basicWeather.properties.minTemperature.values[0].value * 9) / 5 + 32, ), precipitation: - weather.features[0].properties.precipitationLastHour.value || 'N/A', - humidity: Math.round(weather.features[0].properties.relativeHumidity.value), + weather.features[0]?.properties.precipitationLastHour.value || 'N/A', + humidity: Math.round(weather.features[0]?.properties.relativeHumidity.value), heatIndex: Math.round( - (weather.features[0].properties.heatIndex.value * 9) / 5 + 32, + (weather.features[0]?.properties.heatIndex.value * 9) / 5 + 32, ), - windSpeed: Math.round(weather.features[0].properties.windSpeed.value / 1.60934), - windDirection: weather.features[0].properties.windDirection.value, + windSpeed: Math.round(weather.features[0]?.properties.windSpeed.value / 1.60934), + windDirection: weather.features[0]?.properties.windDirection.value, }); } }, [weather, basicWeather]); diff --git a/src/pages/homepage/components/SettingsButton.tsx b/src/pages/homepage/components/SettingsButton.tsx index 9f46c9f..63d3e7f 100644 --- a/src/pages/homepage/components/SettingsButton.tsx +++ b/src/pages/homepage/components/SettingsButton.tsx @@ -13,8 +13,9 @@ import { import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useEffect, useState } from 'react'; import { FaDice } from 'react-icons/fa'; -import { FiMenu, FiMonitor, FiSettings, FiShuffle } from 'react-icons/fi'; +import { FiMenu, FiPower, FiSettings, FiShuffle } from 'react-icons/fi'; import { useNavigate } from 'react-router-dom'; +import { toast } from 'react-toastify'; import { actionIconClasses, buttonClasses, @@ -40,9 +41,10 @@ function SettingsButton() { setBookmarkFilter, isMobile, wolClients, - notify, } = useCommonHooks(); + const notify = (notification: any) => toast(notification, { position: 'bottom-right' }); + const [selectedClient, setSelectedClient] = useState(wolClients?.[0]); const [overlayOpened, setOverlayOpened] = useState(false); const handleWakeRequest = () => { @@ -113,7 +115,7 @@ function SettingsButton() { }} > - +