diff --git a/src/App.tsx b/src/App.tsx index d2ceba2..38a688a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -47,8 +47,14 @@ function App() { const router = createBrowserRouter( createRoutesFromElements( - } /> - } /> + } + /> + } + /> , ), ); diff --git a/src/hooks/useAPI.ts b/src/hooks/useAPI.ts index e14b63d..a87dca4 100644 --- a/src/hooks/useAPI.ts +++ b/src/hooks/useAPI.ts @@ -45,6 +45,8 @@ export function useWeatherAPI({ endpoint }: { endpoint: string }) { url = 'https://api.weather.gov/stations/KSGR/observations'; } else if (endpoint === 'forecast') { url = 'https://api.weather.gov/gridpoints/HGX/55,96/forecast'; + } else if (endpoint === 'hourly') { + url = 'https://api.weather.gov/gridpoints/HGX/55,96/forecast/hourly'; } const callEndpoint = async (endpoint: string) => { const options: object = { @@ -158,7 +160,6 @@ export function useFileUploadAPI() { endpoint: string, body?: object | FormData | undefined | null, ) => { - const options = { method: method, headers: { diff --git a/src/pages/homepage/Homepage.tsx b/src/pages/homepage/Homepage.tsx index acf0a13..a42267d 100644 --- a/src/pages/homepage/Homepage.tsx +++ b/src/pages/homepage/Homepage.tsx @@ -1,13 +1,14 @@ import { Stack } from '@mantine/core'; -import { useLocalStorage } from '@mantine/hooks'; +import { useDisclosure, useLocalStorage } from '@mantine/hooks'; import { useEffect, useState } from 'react'; -import { useQueryAPI } from '../../hooks/useAPI'; +import { useQueryAPI, useWeatherAPI } from '../../hooks/useAPI'; import stackClasses from '../../styles/Stack.module.css'; import Applications from './components/Applications'; import Bookmarks from './components/Bookmarks'; import Header from './components/Header'; import Search from './components/Search'; import SettingsButton from './components/SettingsButton'; +import type { CurrentWeather, OutlookWeather } from './types/weather'; export default function Homepage({ theme }: any) { const { data, isFetching } = useQueryAPI({ endpoint: 'api/messages' }); @@ -20,6 +21,34 @@ export default function Homepage({ theme }: any) { key: 'bookmarkFilter', defaultValue: ['All'], }); + const { data: basicWeather } = useWeatherAPI({ endpoint: 'baseline' }); + const { data: weather } = useWeatherAPI({ endpoint: 'current' }); + const [current, setCurrent] = useState({ + icon: '', + temperature: 0, + highTemp: 0, + lowTemp: 0, + precipitation: 'N/A', + humidity: 0, + heatIndex: 0, + windSpeed: 0, + windDirection: 0, + }); + const [outlook, setOutlook] = useState([ + { + name: '', + icon: '', + isDayTime: true, + temperature: 0, + precipitation: 0, + windSpeed: '0 mph', + windDirection: 'N', + }, + ]); + const { data: forecast } = useWeatherAPI({ endpoint: 'forecast' }); + const { data: hourly } = useWeatherAPI({ endpoint: 'hourly' }); + const [weatherModalOpened, { open: openWeatherModal, close: closeWeatherModal }] = + useDisclosure(false); const isMobile = window.matchMedia('(max-width: 600px)').matches; function date() { const currentDate = new Date(); @@ -59,10 +88,90 @@ export default function Homepage({ theme }: any) { } }, [isFetching]); + useEffect(() => { + if (weather && basicWeather) { + setCurrent({ + icon: weather.features[0].properties.icon?.replace('medium', 'large') || '', + temperature: Math.round( + (weather.features[0].properties.temperature.value * 9) / 5 + 32, + ), + highTemp: Math.round( + (basicWeather.properties.maxTemperature.values[0].value * 9) / 5 + 32, + ), + lowTemp: Math.round( + (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), + heatIndex: Math.round( + (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, + }); + } + }, [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, + }, + ]); + } + }, [forecast]); + return ( - -
+ +
({ - icon: '', - temperature: 0, - highTemp: 0, - lowTemp: 0, - precipitation: 'N/A', - humidity: 0, - heatIndex: 0, - windSpeed: 0, - windDirection: 0, - }); function fixDigits(k: any) { if (k < 10) { return `0${k}`; @@ -107,7 +110,12 @@ function Header({ greet, date, isMobile }: any) { }} > {!isMobile && ( - + + ); } diff --git a/src/pages/homepage/components/HelpModal.tsx b/src/pages/homepage/components/HelpModal.tsx index b341c1c..59bd556 100644 --- a/src/pages/homepage/components/HelpModal.tsx +++ b/src/pages/homepage/components/HelpModal.tsx @@ -14,7 +14,7 @@ export default function HelpModal({ const [theme, setTheme] = useAtom(appTheme); return ( + + + + + {new Date(weather.startTime).toLocaleString('en-US', { + weekday: 'short', + month: 'numeric', + day: 'numeric', + })} + + + {new Date(weather.startTime).toLocaleTimeString('en-US', { + hour: 'numeric', + minute: 'numeric', + })} + + + + + + + +
+ + +
+ + {weather.windSpeed} + +
+
+
+ + {weather.temperature}°{weather.temperatureUnit} + + + ); +} diff --git a/src/pages/homepage/components/Search.tsx b/src/pages/homepage/components/Search.tsx index d04d969..145eab4 100644 --- a/src/pages/homepage/components/Search.tsx +++ b/src/pages/homepage/components/Search.tsx @@ -1,42 +1,23 @@ import { Stack, TextInput } from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; -import { useEffect, useState } from 'react'; -import { useQueryAPI, useWeatherAPI } from '../../../hooks'; +import { useState } from 'react'; +import { useQueryAPI } from '../../../hooks'; import { textInputClasses } from '../../../styles'; import HelpModal from './HelpModal'; import WeatherModal from './WeatherModal'; -export default function Search() { +export default function Search({ + current, + outlook, + hourly, + weatherModalOpened, + closeWeatherModal, + openWeatherModal, +}: any) { const { data: providers } = useQueryAPI({ endpoint: 'api/providers' }); - const { data: basicWeather } = useWeatherAPI({ endpoint: 'baseline' }); - const { data: weather } = useWeatherAPI({ endpoint: 'current' }); - const [current, setCurrent] = useState({ - icon: '', - temperature: 0, - highTemp: 0, - lowTemp: 0, - precipitation: 'N/A', - humidity: 0, - heatIndex: 0, - windSpeed: 0, - windDirection: 0, - }); - const [outlook, setOutlook] = useState([ - { - name: '', - icon: '', - isDayTime: true, - temperature: 0, - precipitation: 0, - windSpeed: '0 mph', - windDirection: 'N', - }, - ]); - const { data: forecast } = useWeatherAPI({ endpoint: 'forecast' }); const [providerModalOpened, { open: openProviderModal, close: closeProviderModal }] = useDisclosure(false); - const [weatherModalOpened, { open: openWeatherModal, close: closeWeatherModal }] = - useDisclosure(false); + const [providerLabel, setProviderLabel] = useState('Google Search'); const [providerURL, setProviderURL] = useState('https://www.google.com/search?q='); const [value, setValue] = useState(''); @@ -88,59 +69,6 @@ export default function Search() { return !!pattern.test(str); } - useEffect(() => { - if (weather && basicWeather) { - setCurrent({ - icon: weather.features[0].properties.icon?.replace('medium', 'large') || '', - temperature: Math.round( - (weather.features[0].properties.temperature.value * 9) / 5 + 32, - ), - highTemp: Math.round( - (basicWeather.properties.maxTemperature.values[0].value * 9) / 5 + 32, - ), - lowTemp: Math.round( - (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), - heatIndex: Math.round( - (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, - }); - } - }, [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, - }, - ]); - } - }, [forecast]); return ( ([]); + const [sunset, setSunset] = useState(100); + + useEffect(() => { + if (hourly) { + setHourlyForecast(hourly.properties.periods.slice(1, 13)); + } + }, [hourly]); + + useEffect(() => { + if (hourlyForecast) { + setSunset( + (hourlyForecast.findIndex((item: any) => item.isDaytime === false) / 12) * 100, + ); + } + }, [hourlyForecast]); return (
- -

Currently

- - - -
- - - {outlook.map((o: any) => ( - -

{o.name}

+ + + +

Currently

- +
- ))} -
+ + + {outlook.map((o: any) => ( + +

{o.name}

+ + + +
+ ))} +
+
+ + 0 && hourlyForecast[0].isDaytime + ? `linear-gradient(90deg, rgba(245, 225, 171, 0.85) ${sunset - 5}%, rgba(35, 28, 42, 0.85) ${sunset + 5}%)` + : `linear-gradient(90deg, rgba(35, 28, 42, 0.85)${sunset - 5}%, rgba(245, 225, 171, 0.85)${sunset + 5}%)`, + padding: '.5rem', + borderRadius: '.5rem', + }} + > + {hourlyForecast.length > 0 && + hourlyForecast.map((h: any) => { + return ; + })} + +
diff --git a/src/pages/homepage/types/weather.d.ts b/src/pages/homepage/types/weather.d.ts index 682f3c9..afc01b5 100644 --- a/src/pages/homepage/types/weather.d.ts +++ b/src/pages/homepage/types/weather.d.ts @@ -1,4 +1,4 @@ -interface CurrentWeather { +export interface CurrentWeather { icon: string; temperature: number; highTemp: number; @@ -10,7 +10,7 @@ interface CurrentWeather { windDirection: number; } -interface OutlookWeather { +export interface OutlookWeather { name: string; icon: string; isDayTime: boolean; diff --git a/vite.config.ts b/vite.config.ts index cb19c90..cf9f50d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,7 +2,7 @@ import react from '@vitejs/plugin-react'; import { defineConfig, loadEnv } from 'vite'; // https://vitejs.dev/config/ -export default defineConfig(({mode}) => { +export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd()); return { plugins: [react()], @@ -28,5 +28,5 @@ export default defineConfig(({mode}) => { }, }, }, -} + }; });