Address updates

This commit is contained in:
Sean Edmunds
2025-01-29 14:36:31 -06:00
parent f9cc1ab63a
commit e87f4abec9
7 changed files with 118 additions and 65 deletions

View File

@@ -1,2 +1,2 @@
export { useQueryAPI, useWeatherAPI, useMutationAPI, useCensusAPI } from './useAPI'; export { useQueryAPI, useWeatherAPI, useMutationAPI, useOSMAPI } from './useAPI';
export { useCommonHooks } from './useCommonHooks'; export { useCommonHooks } from './useCommonHooks';

View File

@@ -1,6 +1,6 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useCommonHooks } from 'hooks'; import { useCallback, useRef } from 'react';
import { useCallback, useState } 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 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() { export function useMutationAPI() {
const { notify } = useCommonHooks(); const toastId = useRef<any>(null);
const [message, setMessage] = useState(''); const notify = () => {
toastId.current = toast('Sending...', { position: 'bottom-right', autoClose: false });
};
const callEndpoint = async ( const callEndpoint = async (
method: string, method: string,
endpoint: string, endpoint: string,
@@ -53,11 +55,9 @@ export function useMutationAPI() {
}, },
body: JSON.stringify(body), body: JSON.stringify(body),
}; };
setMessage('Sending...'); notify();
notify(message);
const response = await fetch(`${url}/${endpoint}`, options); const response = await fetch(`${url}/${endpoint}`, options);
if (!response.ok) { if (!response.ok) {
notify('Error!');
const responseData = await response.json(); const responseData = await response.json();
if (typeof responseData.detail !== 'string') { if (typeof responseData.detail !== 'string') {
const errorResponse = JSON.stringify({ const errorResponse = JSON.stringify({
@@ -69,7 +69,11 @@ export function useMutationAPI() {
loc: responseData.detail[0]?.loc, loc: responseData.detail[0]?.loc,
type: responseData.detail[0]?.type, type: responseData.detail[0]?.type,
}); });
toast.update(toastId.current, {
render: errorResponse,
type: 'error',
autoClose: 5000,
});
throw new Error(errorResponse); throw new Error(errorResponse);
} }
if (typeof responseData.detail === 'object') { if (typeof responseData.detail === 'object') {
@@ -80,7 +84,11 @@ export function useMutationAPI() {
endpoint: endpoint, endpoint: endpoint,
msg: responseData.detail.message, msg: responseData.detail.message,
}); });
toast.update(toastId.current, {
render: errorResponse,
type: 'error',
autoClose: 5000,
});
throw new Error(errorResponse); throw new Error(errorResponse);
} }
const errorResponse = JSON.stringify({ const errorResponse = JSON.stringify({
@@ -90,10 +98,18 @@ export function useMutationAPI() {
endpoint: endpoint, endpoint: endpoint,
msg: responseData.detail, msg: responseData.detail,
}); });
toast.update(toastId.current, {
render: errorResponse,
type: 'error',
autoClose: 5000,
});
throw new Error(errorResponse); throw new Error(errorResponse);
} }
setMessage('Success!'); toast.update(toastId.current, {
render: 'Success!',
autoClose: 2000,
transition: Flip,
});
return await response.json(); 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 = ''; let url = '';
if (endpoint === 'baseline') { 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') { } else if (endpoint === 'current') {
url = 'https://api.weather.gov/stations/KSGR/observations'; url = `https://api.weather.gov/stations/${station}/observations`;
} else if (endpoint === 'forecast') { } 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') { } 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 callEndpoint = async () => {
const response = await fetch(url); const response = await fetch(url);
@@ -142,15 +178,15 @@ export function useWeatherAPI({ endpoint }: { endpoint: string }) {
}); });
} }
export function useCensusAPI({ address }: { address: string }) { export function useOSMAPI({ address }: { address: string }) {
const url = `https://geocoding.geo.census.gov/geocoder/locations/onelineaddress?address=${address?.replaceAll(' ', '+').replaceAll(',', '%2C').replaceAll('#', '%23')}&benchmark=4&format=json`; const url = `https://nominatim.openstreetmap.org/search?addressdetails=1&format=jsonv2&limit=1&q=${address?.replaceAll(' ', '+')}`;
const callEndpoint = async () => { const callEndpoint = async () => {
const response = await fetch(url); const response = await fetch(url);
if (!response.ok) { if (!response.ok) {
throw new Error(`${response.status} ${response.statusText}`); throw new Error(`${response.status} ${response.statusText}`);
} }
return await response.json(); const data = await response.json();
return await data;
}; };
return useQuery({ return useQuery({

View File

@@ -2,8 +2,7 @@ import { useLocalStorage } from '@mantine/hooks';
import { appTheme } from 'contexts'; import { appTheme } from 'contexts';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { toast } from 'react-toastify'; import { useOSMAPI, useQueryAPI, useWeatherAPI } from './useAPI';
import { useCensusAPI, useQueryAPI, useWeatherAPI } from './useAPI';
export function useCommonHooks() { export function useCommonHooks() {
const isMobile = window.matchMedia('(max-width: 600px)').matches; const isMobile = window.matchMedia('(max-width: 600px)').matches;
@@ -62,10 +61,7 @@ export function useCommonHooks() {
isFetching: wolClientsIsFetching, isFetching: wolClientsIsFetching,
isLoading: wolClientsIsLoading, isLoading: wolClientsIsLoading,
} = useQueryAPI({ endpoint: 'api/wolClients' }); } = 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 [theme, setTheme] = useAtom(appTheme);
const [themeName, setThemeName] = useLocalStorage<any>({ const [themeName, setThemeName] = useLocalStorage<any>({
key: 'theme', key: 'theme',
@@ -102,8 +98,6 @@ export function useCommonHooks() {
const [applicationData, setApplicationData] = useState<any>([]); const [applicationData, setApplicationData] = useState<any>([]);
const notify = (notification: any) => toast(notification, { position: 'bottom-right' });
useEffect(() => { useEffect(() => {
if (apps && !isAppsFetching) { if (apps && !isAppsFetching) {
setApplicationData(apps); setApplicationData(apps);
@@ -161,17 +155,40 @@ export function useCommonHooks() {
} }
}, [wolClients, wolClientsIsFetched]); }, [wolClients, wolClientsIsFetched]);
const { data: locationData } = useCensusAPI({ const { data: weatherMetadata } = useOSMAPI({ address: personal?.[0].physicalAddress });
address: personalData?.[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<any>([]);
useEffect(() => {
if (locationData) {
setLocation(locationData);
}
}, [locationData]);
return { return {
isMobile, isMobile,
@@ -234,7 +251,5 @@ export function useCommonHooks() {
wolClientsIsFetched, wolClientsIsFetched,
wolClientsIsFetching, wolClientsIsFetching,
wolClientsIsLoading, wolClientsIsLoading,
location,
notify,
}; };
} }

View File

@@ -79,11 +79,11 @@ export function Homepage() {
}, [messages, isMessagesFetching]); }, [messages, isMessagesFetching]);
useEffect(() => { useEffect(() => {
if (weather && basicWeather) { if (weather !== undefined && basicWeather !== undefined) {
setCurrent({ setCurrent({
icon: weather.features[0].properties.icon?.replace('medium', 'large') || '', icon: weather.features[0]?.properties.icon?.replace('medium', 'large') || '',
temperature: Math.round( temperature: Math.round(
(weather.features[0].properties.temperature.value * 9) / 5 + 32, (weather.features[0]?.properties.temperature.value * 9) / 5 + 32,
), ),
highTemp: Math.round( highTemp: Math.round(
(basicWeather.properties.maxTemperature.values[0].value * 9) / 5 + 32, (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, (basicWeather.properties.minTemperature.values[0].value * 9) / 5 + 32,
), ),
precipitation: 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), humidity: Math.round(weather.features[0]?.properties.relativeHumidity.value),
heatIndex: Math.round( heatIndex: Math.round(
weather.features[0].properties.heatIndex.value !== null weather.features[0]?.properties.heatIndex.value !== null
? (weather.features[0].properties.heatIndex.value * 9) / 5 + 32 ? (weather.features[0]?.properties.heatIndex.value * 9) / 5 + 32
: (weather.features[0].properties.temperature.value * 9) / 5 + 32, : (weather.features[0]?.properties.temperature.value * 9) / 5 + 32,
), ),
windSpeed: Math.round(weather.features[0].properties.windSpeed.value / 1.60934), windSpeed: Math.round(weather.features[0]?.properties.windSpeed.value / 1.60934),
windDirection: weather.features[0].properties.windDirection.value, windDirection: weather.features[0]?.properties.windDirection.value,
}); });
} }
}, [weather, basicWeather]); }, [weather, basicWeather]);

View File

@@ -31,11 +31,11 @@ function Header({
}, []); }, []);
useEffect(() => { useEffect(() => {
if (weather && basicWeather) { if (weather !== undefined && basicWeather !== undefined) {
setCurrent({ setCurrent({
icon: weather.features[0].properties.icon?.replace('medium', 'large') || '', icon: weather.features[0]?.properties.icon?.replace('medium', 'large') || '',
temperature: Math.round( temperature: Math.round(
(weather.features[0].properties.temperature.value * 9) / 5 + 32, (weather.features[0]?.properties.temperature.value * 9) / 5 + 32,
), ),
highTemp: Math.round( highTemp: Math.round(
(basicWeather.properties.maxTemperature.values[0].value * 9) / 5 + 32, (basicWeather.properties.maxTemperature.values[0].value * 9) / 5 + 32,
@@ -44,13 +44,13 @@ function Header({
(basicWeather.properties.minTemperature.values[0].value * 9) / 5 + 32, (basicWeather.properties.minTemperature.values[0].value * 9) / 5 + 32,
), ),
precipitation: 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), humidity: Math.round(weather.features[0]?.properties.relativeHumidity.value),
heatIndex: Math.round( 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), windSpeed: Math.round(weather.features[0]?.properties.windSpeed.value / 1.60934),
windDirection: weather.features[0].properties.windDirection.value, windDirection: weather.features[0]?.properties.windDirection.value,
}); });
} }
}, [weather, basicWeather]); }, [weather, basicWeather]);

View File

@@ -13,8 +13,9 @@ import {
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { FaDice } from 'react-icons/fa'; 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 { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify';
import { import {
actionIconClasses, actionIconClasses,
buttonClasses, buttonClasses,
@@ -40,9 +41,10 @@ function SettingsButton() {
setBookmarkFilter, setBookmarkFilter,
isMobile, isMobile,
wolClients, wolClients,
notify,
} = useCommonHooks(); } = useCommonHooks();
const notify = (notification: any) => toast(notification, { position: 'bottom-right' });
const [selectedClient, setSelectedClient] = useState<any>(wolClients?.[0]); const [selectedClient, setSelectedClient] = useState<any>(wolClients?.[0]);
const [overlayOpened, setOverlayOpened] = useState(false); const [overlayOpened, setOverlayOpened] = useState(false);
const handleWakeRequest = () => { const handleWakeRequest = () => {
@@ -113,7 +115,7 @@ function SettingsButton() {
}} }}
> >
<Stack gap={0} align='center'> <Stack gap={0} align='center'>
<FiMonitor size={40} /> <FiPower size={32} />
</Stack> </Stack>
</Button> </Button>
<Select <Select

View File

@@ -38,9 +38,9 @@
&.root { &.root {
font-size: 1em; font-size: 1em;
font-weight:500; font-weight:500;
margin: 10px; margin-bottom: 10px;
width:108px; width:72px;
height:96px; height:72px;
line-height: 3rem; line-height: 3rem;
text-align: center; text-align: center;
text-transform: uppercase; text-transform: uppercase;