Hourly forecast
This commit is contained in:
10
src/App.tsx
10
src/App.tsx
@@ -47,8 +47,14 @@ function App() {
|
|||||||
const router = createBrowserRouter(
|
const router = createBrowserRouter(
|
||||||
createRoutesFromElements(
|
createRoutesFromElements(
|
||||||
<Route>
|
<Route>
|
||||||
<Route path={`/${import.meta.env.VITE_BASE_URL}`} element={<Homepage theme={theme} />} />
|
<Route
|
||||||
<Route path={`/${import.meta.env.VITE_BASE_URL}/settings`} element={<Settings theme={theme} />} />
|
path={`/${import.meta.env.VITE_BASE_URL}`}
|
||||||
|
element={<Homepage theme={theme} />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={`/${import.meta.env.VITE_BASE_URL}/settings`}
|
||||||
|
element={<Settings theme={theme} />}
|
||||||
|
/>
|
||||||
</Route>,
|
</Route>,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ export function useWeatherAPI({ endpoint }: { endpoint: string }) {
|
|||||||
url = 'https://api.weather.gov/stations/KSGR/observations';
|
url = 'https://api.weather.gov/stations/KSGR/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/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 callEndpoint = async (endpoint: string) => {
|
||||||
const options: object = {
|
const options: object = {
|
||||||
@@ -158,7 +160,6 @@ export function useFileUploadAPI() {
|
|||||||
endpoint: string,
|
endpoint: string,
|
||||||
body?: object | FormData | undefined | null,
|
body?: object | FormData | undefined | null,
|
||||||
) => {
|
) => {
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
method: method,
|
method: method,
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { Stack } from '@mantine/core';
|
import { Stack } from '@mantine/core';
|
||||||
import { useLocalStorage } from '@mantine/hooks';
|
import { useDisclosure, useLocalStorage } from '@mantine/hooks';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useQueryAPI } from '../../hooks/useAPI';
|
import { useQueryAPI, useWeatherAPI } from '../../hooks/useAPI';
|
||||||
import stackClasses from '../../styles/Stack.module.css';
|
import stackClasses from '../../styles/Stack.module.css';
|
||||||
import Applications from './components/Applications';
|
import Applications from './components/Applications';
|
||||||
import Bookmarks from './components/Bookmarks';
|
import Bookmarks from './components/Bookmarks';
|
||||||
import Header from './components/Header';
|
import Header from './components/Header';
|
||||||
import Search from './components/Search';
|
import Search from './components/Search';
|
||||||
import SettingsButton from './components/SettingsButton';
|
import SettingsButton from './components/SettingsButton';
|
||||||
|
import type { CurrentWeather, OutlookWeather } from './types/weather';
|
||||||
|
|
||||||
export default function Homepage({ theme }: any) {
|
export default function Homepage({ theme }: any) {
|
||||||
const { data, isFetching } = useQueryAPI({ endpoint: 'api/messages' });
|
const { data, isFetching } = useQueryAPI({ endpoint: 'api/messages' });
|
||||||
@@ -20,6 +21,34 @@ export default function Homepage({ theme }: any) {
|
|||||||
key: 'bookmarkFilter',
|
key: 'bookmarkFilter',
|
||||||
defaultValue: ['All'],
|
defaultValue: ['All'],
|
||||||
});
|
});
|
||||||
|
const { data: basicWeather } = useWeatherAPI({ endpoint: 'baseline' });
|
||||||
|
const { data: weather } = useWeatherAPI({ endpoint: 'current' });
|
||||||
|
const [current, setCurrent] = useState<CurrentWeather>({
|
||||||
|
icon: '',
|
||||||
|
temperature: 0,
|
||||||
|
highTemp: 0,
|
||||||
|
lowTemp: 0,
|
||||||
|
precipitation: 'N/A',
|
||||||
|
humidity: 0,
|
||||||
|
heatIndex: 0,
|
||||||
|
windSpeed: 0,
|
||||||
|
windDirection: 0,
|
||||||
|
});
|
||||||
|
const [outlook, setOutlook] = useState<OutlookWeather[]>([
|
||||||
|
{
|
||||||
|
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;
|
const isMobile = window.matchMedia('(max-width: 600px)').matches;
|
||||||
function date() {
|
function date() {
|
||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
@@ -59,10 +88,90 @@ export default function Homepage({ theme }: any) {
|
|||||||
}
|
}
|
||||||
}, [isFetching]);
|
}, [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 (
|
return (
|
||||||
<Stack gap={0} className={stackClasses.containerStack} justify='center'>
|
<Stack gap={0} className={stackClasses.containerStack} justify='center'>
|
||||||
<Search />
|
<Search
|
||||||
<Header date={date()} greet={displayedGreeting} isMobile={isMobile} />
|
current={current}
|
||||||
|
setCurrent={setCurrent}
|
||||||
|
outlook={outlook}
|
||||||
|
setOutlook={setOutlook}
|
||||||
|
weather={weather}
|
||||||
|
basicWeather={basicWeather}
|
||||||
|
forecast={forecast}
|
||||||
|
hourly={hourly}
|
||||||
|
weatherModalOpened={weatherModalOpened}
|
||||||
|
closeWeatherModal={closeWeatherModal}
|
||||||
|
openWeatherModal={openWeatherModal}
|
||||||
|
/>
|
||||||
|
<Header
|
||||||
|
date={date()}
|
||||||
|
greet={displayedGreeting}
|
||||||
|
isMobile={isMobile}
|
||||||
|
current={current}
|
||||||
|
outlook={outlook}
|
||||||
|
setCurrent={setCurrent}
|
||||||
|
weather={weather}
|
||||||
|
basicWeather={basicWeather}
|
||||||
|
forecast={forecast}
|
||||||
|
hourly={hourly}
|
||||||
|
weatherModalOpened={weatherModalOpened}
|
||||||
|
closeWeatherModal={closeWeatherModal}
|
||||||
|
openWeatherModal={openWeatherModal}
|
||||||
|
/>
|
||||||
<Applications contentType={appFilter} isMobile={isMobile} />
|
<Applications contentType={appFilter} isMobile={isMobile} />
|
||||||
<Bookmarks contentType={bookmarkFilter} isMobile={isMobile} />
|
<Bookmarks contentType={bookmarkFilter} isMobile={isMobile} />
|
||||||
<SettingsButton
|
<SettingsButton
|
||||||
|
|||||||
@@ -1,23 +1,26 @@
|
|||||||
import { Group, Stack, Text } from '@mantine/core';
|
import { Group, Stack, Text } from '@mantine/core';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useWeatherAPI } from '../../../hooks';
|
|
||||||
import { textClasses } from '../../../styles';
|
import { textClasses } from '../../../styles';
|
||||||
|
import WeatherModal from './WeatherModal';
|
||||||
|
|
||||||
function Header({ greet, date, isMobile }: any) {
|
function Header({
|
||||||
|
greet,
|
||||||
|
date,
|
||||||
|
isMobile,
|
||||||
|
current,
|
||||||
|
setCurrent,
|
||||||
|
weather,
|
||||||
|
basicWeather,
|
||||||
|
outlook,
|
||||||
|
hourly,
|
||||||
|
weatherModalOpened,
|
||||||
|
closeWeatherModal,
|
||||||
|
openWeatherModal,
|
||||||
|
setValue,
|
||||||
|
setProviderLabel,
|
||||||
|
setProviderURL,
|
||||||
|
}: any) {
|
||||||
const [time, setTime] = useState(new Date());
|
const [time, setTime] = useState(new Date());
|
||||||
const { data: weather } = useWeatherAPI({ endpoint: 'current' });
|
|
||||||
const { data: basicWeather } = useWeatherAPI({ endpoint: 'baseline' });
|
|
||||||
const [current, setCurrent] = useState<CurrentWeather>({
|
|
||||||
icon: '',
|
|
||||||
temperature: 0,
|
|
||||||
highTemp: 0,
|
|
||||||
lowTemp: 0,
|
|
||||||
precipitation: 'N/A',
|
|
||||||
humidity: 0,
|
|
||||||
heatIndex: 0,
|
|
||||||
windSpeed: 0,
|
|
||||||
windDirection: 0,
|
|
||||||
});
|
|
||||||
function fixDigits(k: any) {
|
function fixDigits(k: any) {
|
||||||
if (k < 10) {
|
if (k < 10) {
|
||||||
return `0${k}`;
|
return `0${k}`;
|
||||||
@@ -107,7 +110,12 @@ function Header({ greet, date, isMobile }: any) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<Group gap={0} align='end' style={{ display: 'flex' }}>
|
<Group
|
||||||
|
gap={0}
|
||||||
|
align='end'
|
||||||
|
style={{ display: 'flex' }}
|
||||||
|
onClick={openWeatherModal}
|
||||||
|
>
|
||||||
<Text
|
<Text
|
||||||
component='h2'
|
component='h2'
|
||||||
mr={4}
|
mr={4}
|
||||||
@@ -137,6 +145,16 @@ function Header({ greet, date, isMobile }: any) {
|
|||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
<WeatherModal
|
||||||
|
current={current}
|
||||||
|
outlook={outlook}
|
||||||
|
hourly={hourly}
|
||||||
|
closeWeatherModal={closeWeatherModal}
|
||||||
|
weatherModalOpened={weatherModalOpened}
|
||||||
|
setValue={setValue}
|
||||||
|
setProviderLabel={setProviderLabel}
|
||||||
|
setProviderURL={setProviderURL}
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default function HelpModal({
|
|||||||
const [theme, setTheme] = useAtom(appTheme);
|
const [theme, setTheme] = useAtom(appTheme);
|
||||||
return (
|
return (
|
||||||
<Modal.Root
|
<Modal.Root
|
||||||
size={'50%'}
|
size={'60%'}
|
||||||
className={modalClasses.helpModal}
|
className={modalClasses.helpModal}
|
||||||
classNames={modalClasses}
|
classNames={modalClasses}
|
||||||
opened={providerModalOpened}
|
opened={providerModalOpened}
|
||||||
|
|||||||
100
src/pages/homepage/components/HourlyCard.tsx
Normal file
100
src/pages/homepage/components/HourlyCard.tsx
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { Icon } from '@iconify/react/dist/iconify.js';
|
||||||
|
import { Box, Card, Center, Image, Stack, Text } from '@mantine/core';
|
||||||
|
|
||||||
|
export enum WindDirection {
|
||||||
|
N = 0,
|
||||||
|
NE = 45,
|
||||||
|
E = 90,
|
||||||
|
SE = 135,
|
||||||
|
S = 180,
|
||||||
|
SW = 225,
|
||||||
|
W = 270,
|
||||||
|
NW = 315,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function HourlyCard({ weather }: any) {
|
||||||
|
return (
|
||||||
|
<Stack gap={0} align='center'>
|
||||||
|
<Card
|
||||||
|
style={{
|
||||||
|
height: '8rem',
|
||||||
|
width: '4rem',
|
||||||
|
backgroundColor: 'var(--color-text-pri)',
|
||||||
|
background: 'rgba(255, 255, 255, 0.6)',
|
||||||
|
padding: 0,
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack gap={0} h={'100%'} w={'100%'} align='center'>
|
||||||
|
<Stack gap={0} align='center'>
|
||||||
|
<Text mb={-6} fz={12} style={{ zIndex: 2 }}>
|
||||||
|
{new Date(weather.startTime).toLocaleString('en-US', {
|
||||||
|
weekday: 'short',
|
||||||
|
month: 'numeric',
|
||||||
|
day: 'numeric',
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
<Text fz={12} style={{ zIndex: 2 }}>
|
||||||
|
{new Date(weather.startTime).toLocaleTimeString('en-US', {
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
<Stack gap={0} align='center'>
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
width: '56px',
|
||||||
|
height: '56px',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
boxShadow:
|
||||||
|
'inset 0px 0px 10px 3px rgba(255, 255, 255, 1), 0px 0px 10px 0px rgba(255, 255, 255, .6)',
|
||||||
|
zIndex: 1,
|
||||||
|
borderRadius: '100%',
|
||||||
|
borderStyle: 'none',
|
||||||
|
border: '0px solid',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Image
|
||||||
|
src={weather.icon}
|
||||||
|
style={{
|
||||||
|
zIndex: 0,
|
||||||
|
borderRadius: '100%',
|
||||||
|
border: '0px solid',
|
||||||
|
borderStyle: 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
<Stack gap={0} align='center' style={{ zIndex: 1 }}>
|
||||||
|
<Center>
|
||||||
|
<Icon
|
||||||
|
icon={'mdi:navigation'}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
transform: `rotate(${WindDirection[weather.windDirection]}deg)`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Icon fontSize={28} icon={'mdi:circle-outline'} />
|
||||||
|
</Center>
|
||||||
|
<Text mt={-6} fz={12}>
|
||||||
|
{weather.windSpeed}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
<Text
|
||||||
|
mt={4}
|
||||||
|
w={'100%'}
|
||||||
|
fz={24}
|
||||||
|
style={{
|
||||||
|
textAlign: 'center',
|
||||||
|
borderRadius: '4px',
|
||||||
|
background: 'rgba(0, 0, 0, 0.6)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{weather.temperature}°{weather.temperatureUnit}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,42 +1,23 @@
|
|||||||
import { Stack, TextInput } from '@mantine/core';
|
import { Stack, TextInput } from '@mantine/core';
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useQueryAPI, useWeatherAPI } from '../../../hooks';
|
import { useQueryAPI } from '../../../hooks';
|
||||||
import { textInputClasses } from '../../../styles';
|
import { textInputClasses } from '../../../styles';
|
||||||
import HelpModal from './HelpModal';
|
import HelpModal from './HelpModal';
|
||||||
import WeatherModal from './WeatherModal';
|
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: providers } = useQueryAPI({ endpoint: 'api/providers' });
|
||||||
const { data: basicWeather } = useWeatherAPI({ endpoint: 'baseline' });
|
|
||||||
const { data: weather } = useWeatherAPI({ endpoint: 'current' });
|
|
||||||
const [current, setCurrent] = useState<CurrentWeather>({
|
|
||||||
icon: '',
|
|
||||||
temperature: 0,
|
|
||||||
highTemp: 0,
|
|
||||||
lowTemp: 0,
|
|
||||||
precipitation: 'N/A',
|
|
||||||
humidity: 0,
|
|
||||||
heatIndex: 0,
|
|
||||||
windSpeed: 0,
|
|
||||||
windDirection: 0,
|
|
||||||
});
|
|
||||||
const [outlook, setOutlook] = useState<OutlookWeather[]>([
|
|
||||||
{
|
|
||||||
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 }] =
|
const [providerModalOpened, { open: openProviderModal, close: closeProviderModal }] =
|
||||||
useDisclosure(false);
|
useDisclosure(false);
|
||||||
const [weatherModalOpened, { open: openWeatherModal, close: closeWeatherModal }] =
|
|
||||||
useDisclosure(false);
|
|
||||||
const [providerLabel, setProviderLabel] = useState('Google Search');
|
const [providerLabel, setProviderLabel] = useState('Google Search');
|
||||||
const [providerURL, setProviderURL] = useState('https://www.google.com/search?q=');
|
const [providerURL, setProviderURL] = useState('https://www.google.com/search?q=');
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
@@ -88,59 +69,6 @@ export default function Search() {
|
|||||||
return !!pattern.test(str);
|
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 (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
style={{
|
style={{
|
||||||
@@ -201,6 +129,7 @@ export default function Search() {
|
|||||||
<WeatherModal
|
<WeatherModal
|
||||||
current={current}
|
current={current}
|
||||||
outlook={outlook}
|
outlook={outlook}
|
||||||
|
hourly={hourly}
|
||||||
closeWeatherModal={closeWeatherModal}
|
closeWeatherModal={closeWeatherModal}
|
||||||
weatherModalOpened={weatherModalOpened}
|
weatherModalOpened={weatherModalOpened}
|
||||||
setValue={setValue}
|
setValue={setValue}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Icon } from '@iconify/react/dist/iconify.js';
|
import { Icon } from '@iconify/react/dist/iconify.js';
|
||||||
import { Box, Center, Image, Overlay, Stack, Text } from '@mantine/core';
|
import { Box, Center, Image, Overlay, Stack, Text } from '@mantine/core';
|
||||||
|
|
||||||
enum WindDirection {
|
export enum WindDirection {
|
||||||
N = 0,
|
N = 0,
|
||||||
NE = 45,
|
NE = 45,
|
||||||
E = 90,
|
E = 90,
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ import { modalClasses } from '../../../styles';
|
|||||||
import SmallOutlookCard from './SmallOutlookCard';
|
import SmallOutlookCard from './SmallOutlookCard';
|
||||||
import SmallWeatherCard from './SmallWeatherCard';
|
import SmallWeatherCard from './SmallWeatherCard';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
import { appTheme } from '../../../contexts/theme';
|
import { appTheme } from '../../../contexts/theme';
|
||||||
|
import HourlyCard from './HourlyCard';
|
||||||
|
|
||||||
export default function WeatherModal({
|
export default function WeatherModal({
|
||||||
current,
|
current,
|
||||||
outlook,
|
outlook,
|
||||||
|
hourly,
|
||||||
closeWeatherModal,
|
closeWeatherModal,
|
||||||
weatherModalOpened,
|
weatherModalOpened,
|
||||||
setValue,
|
setValue,
|
||||||
@@ -16,9 +19,25 @@ export default function WeatherModal({
|
|||||||
setProviderURL,
|
setProviderURL,
|
||||||
}: any) {
|
}: any) {
|
||||||
const [theme, setTheme] = useAtom(appTheme);
|
const [theme, setTheme] = useAtom(appTheme);
|
||||||
|
const [hourlyForecast, setHourlyForecast] = useState<any>([]);
|
||||||
|
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 (
|
return (
|
||||||
<Modal.Root
|
<Modal.Root
|
||||||
size={'50%'}
|
size={'60%'}
|
||||||
className={modalClasses.helpModal}
|
className={modalClasses.helpModal}
|
||||||
classNames={modalClasses}
|
classNames={modalClasses}
|
||||||
opened={weatherModalOpened}
|
opened={weatherModalOpened}
|
||||||
@@ -47,13 +66,15 @@ export default function WeatherModal({
|
|||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body pb={'2rem'}>
|
<Modal.Body pb={'2rem'}>
|
||||||
<Center w={'100%'}>
|
<Center w={'100%'}>
|
||||||
|
<Stack>
|
||||||
|
<Group gap={0} w={'100%'} align='center' justify='center'>
|
||||||
<Stack gap={0} align='center'>
|
<Stack gap={0} align='center'>
|
||||||
<h3 style={{ padding: 0, margin: 0 }}>Currently</h3>
|
<h3 style={{ padding: 0, margin: 0 }}>Currently</h3>
|
||||||
<Stack>
|
<Stack>
|
||||||
<SmallWeatherCard weather={current} />
|
<SmallWeatherCard weather={current} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Divider orientation='vertical' mx={8} color={theme.accentColor} />
|
<Divider orientation='vertical' mx={16} color={theme.accentColor} />
|
||||||
<Group gap={8}>
|
<Group gap={8}>
|
||||||
{outlook.map((o: any) => (
|
{outlook.map((o: any) => (
|
||||||
<Stack key={o.name} gap={0} align='center'>
|
<Stack key={o.name} gap={0} align='center'>
|
||||||
@@ -64,6 +85,24 @@ export default function WeatherModal({
|
|||||||
</Stack>
|
</Stack>
|
||||||
))}
|
))}
|
||||||
</Group>
|
</Group>
|
||||||
|
</Group>
|
||||||
|
<Divider orientation='horizontal' mx={8} color={theme.accentColor} />
|
||||||
|
<Group
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
hourlyForecast.length > 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 <HourlyCard key={h.time} weather={h} />;
|
||||||
|
})}
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
</Center>
|
</Center>
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
</Modal.Content>
|
</Modal.Content>
|
||||||
|
|||||||
4
src/pages/homepage/types/weather.d.ts
vendored
4
src/pages/homepage/types/weather.d.ts
vendored
@@ -1,4 +1,4 @@
|
|||||||
interface CurrentWeather {
|
export interface CurrentWeather {
|
||||||
icon: string;
|
icon: string;
|
||||||
temperature: number;
|
temperature: number;
|
||||||
highTemp: number;
|
highTemp: number;
|
||||||
@@ -10,7 +10,7 @@ interface CurrentWeather {
|
|||||||
windDirection: number;
|
windDirection: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OutlookWeather {
|
export interface OutlookWeather {
|
||||||
name: string;
|
name: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
isDayTime: boolean;
|
isDayTime: boolean;
|
||||||
|
|||||||
@@ -28,5 +28,5 @@ export default defineConfig(({mode}) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user