diff --git a/src/Api/weatherApi.js b/src/Api/weatherApi.js index d676624e..be459115 100644 --- a/src/Api/weatherApi.js +++ b/src/Api/weatherApi.js @@ -14,7 +14,13 @@ export const getCurrentWeather = async (longitude, latitude) => { export const getLocationByCoords = async (longitude, latitude) => { const result = await instance.get(`/location/${longitude},${latitude}`); return { + id: result.data.id, city: result.data.name, country: result.data.country }; }; + +export const getHourlyWeather = async (longitude, latitude) => { + const result = await instance.get(`/forecast/hourly/${longitude},${latitude}`); + return result.data.forecast; +}; diff --git a/src/Components/CurrentLocation/CurrentLocation.jsx b/src/Components/CurrentLocation/CurrentLocation.jsx new file mode 100644 index 00000000..8abe6bfb --- /dev/null +++ b/src/Components/CurrentLocation/CurrentLocation.jsx @@ -0,0 +1,9 @@ +function CurrentLocation({ currentLocation }) { + return ( +

+ {currentLocation.city}, {currentLocation.country} +

+ ); +} + +export default CurrentLocation; diff --git a/src/Components/HourlyWeatherItem/HourlyWeatherItem.jsx b/src/Components/HourlyWeatherItem/HourlyWeatherItem.jsx new file mode 100644 index 00000000..0ce00449 --- /dev/null +++ b/src/Components/HourlyWeatherItem/HourlyWeatherItem.jsx @@ -0,0 +1,15 @@ +import { toReadableTime } from '../../helpers/formatHelper'; +import SafeWeatherImage from '../SafeImage/SafeWeatherImage'; + +function HourlyWeatherItem({ hourly }) { + return ( +
+

+ {toReadableTime(hourly.time)} temperature: {hourly.temperature}° precipProb:{' '} + {hourly.precipProb} % +

+
+ ); +} + +export default HourlyWeatherItem; diff --git a/src/Components/Pages/DetailedForesastPage/DetailedForecastPage.jsx b/src/Components/Pages/DetailedForesastPage/DetailedForecastPage.jsx index f0235d3f..cc700dc6 100644 --- a/src/Components/Pages/DetailedForesastPage/DetailedForecastPage.jsx +++ b/src/Components/Pages/DetailedForesastPage/DetailedForecastPage.jsx @@ -1,7 +1,31 @@ +import { useEffect } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { getLocation } from '../../../Store/selectors/geoSelectors'; +import HourlyWeatherItem from '../../HourlyWeatherItem/HourlyWeatherItem'; +import { fetchUserLocationDetailedWeather } from './detailedForecastPageActions'; +import { getHourlyWeather, getLoading } from './detailedForecastPageSelectors'; +import Loader from '../../Loader/Loader'; +import CurrentLocation from '../../CurrentLocation/CurrentLocation'; + function DetailedForecastPage() { + const dispatch = useDispatch(); + const location = useSelector(getLocation); + const hourlyWeather = useSelector(getHourlyWeather); + const loading = useSelector(getLoading); + + useEffect(async () => { + dispatch(fetchUserLocationDetailedWeather()); + }, []); + return (

Detailed Page

+ {loading && } + {location && } + {hourlyWeather && + hourlyWeather.map(hourlyItem => ( + + ))}
); } diff --git a/src/Components/Pages/DetailedForesastPage/detailedForecastPageActions.js b/src/Components/Pages/DetailedForesastPage/detailedForecastPageActions.js new file mode 100644 index 00000000..438f44dd --- /dev/null +++ b/src/Components/Pages/DetailedForesastPage/detailedForecastPageActions.js @@ -0,0 +1,36 @@ +import { fetchUserGeoData } from '../../../Store/actions/geoActions'; +import { getPosition } from '../../../Store/selectors/geoSelectors'; +import { getHourlyWeather } from '../../../Api/weatherApi'; + +export const DETAILED_FORECAST_PAGE_ACTIONS = { + USER_POSITION_DETAILED_WEATHER_REQUESTED: 'USER_POSITION_DETAILED_WEATHER_REQUESTED', + USER_POSITION_DETAILED_WEATHER_RECEIVED: 'USER_POSITION_DETAILED_WEATHER_RECEIVED', + USER_POSITION_DETAILED_WEATHER_FAILED: 'USER_POSITION_DETAILED_WEATHER_FAILED' +}; + +const userPositionDetailedWeatherRequested = () => ({ + type: DETAILED_FORECAST_PAGE_ACTIONS.USER_POSITION_DETAILED_WEATHER_REQUESTED +}); + +const userPositionDetailedWeatherReceived = ({ hourlyWeather }) => ({ + type: DETAILED_FORECAST_PAGE_ACTIONS.USER_POSITION_DETAILED_WEATHER_RECEIVED, + payload: { hourlyWeather } +}); + +const userPositionDetailedWeatherFailed = ({ error }) => ({ + type: DETAILED_FORECAST_PAGE_ACTIONS.USER_POSITION_DETAILED_WEATHER_FAILED, + payload: error +}); + +export const fetchUserLocationDetailedWeather = () => async (dispatch, getState) => { + dispatch(userPositionDetailedWeatherRequested()); + + try { + await dispatch(fetchUserGeoData()); + const { longitude, latitude } = getPosition(getState()); + const hourlyWeather = await getHourlyWeather(longitude, latitude); + dispatch(userPositionDetailedWeatherReceived({ hourlyWeather })); + } catch (error) { + dispatch(userPositionDetailedWeatherFailed(error)); + } +}; diff --git a/src/Components/Pages/DetailedForesastPage/detailedForecastPageInitialState.js b/src/Components/Pages/DetailedForesastPage/detailedForecastPageInitialState.js new file mode 100644 index 00000000..89a51f6f --- /dev/null +++ b/src/Components/Pages/DetailedForesastPage/detailedForecastPageInitialState.js @@ -0,0 +1,7 @@ +const initialState = { + hourlyWeather: null, + loading: false, + error: null +}; + +export default initialState; diff --git a/src/Components/Pages/DetailedForesastPage/detailedForecastPageReducer.js b/src/Components/Pages/DetailedForesastPage/detailedForecastPageReducer.js new file mode 100644 index 00000000..aa71bcc8 --- /dev/null +++ b/src/Components/Pages/DetailedForesastPage/detailedForecastPageReducer.js @@ -0,0 +1,27 @@ +import initialState from './detailedForecastPageInitialState'; +import { DETAILED_FORECAST_PAGE_ACTIONS } from './detailedForecastPageActions'; +import produce from 'immer'; + +export const detailedForecastPageReducer = (state = initialState, { type, payload }) => { + switch (type) { + case DETAILED_FORECAST_PAGE_ACTIONS.USER_POSITION_DETAILED_WEATHER_REQUESTED: + return produce(state, draft => { + draft.loading = true; + draft.error = null; + }); + case DETAILED_FORECAST_PAGE_ACTIONS.USER_POSITION_DETAILED_WEATHER_RECEIVED: + return produce(state, draft => { + draft.loading = false; + draft.hourlyWeather = payload.hourlyWeather; + }); + case DETAILED_FORECAST_PAGE_ACTIONS.USER_POSITION_DETAILED_WEATHER_FAILED: + return produce(state, draft => { + draft.loading = false; + draft.error = payload; + }); + default: + return state; + } +}; + +export default detailedForecastPageReducer; diff --git a/src/Components/Pages/DetailedForesastPage/detailedForecastPageSelectors.js b/src/Components/Pages/DetailedForesastPage/detailedForecastPageSelectors.js new file mode 100644 index 00000000..57b3b419 --- /dev/null +++ b/src/Components/Pages/DetailedForesastPage/detailedForecastPageSelectors.js @@ -0,0 +1,2 @@ +export const getHourlyWeather = state => state.detailedForecastPage.hourlyWeather; +export const getLoading = state => state.detailedForecastPage.loading; diff --git a/src/Components/Pages/HomePage/HomePage.jsx b/src/Components/Pages/HomePage/HomePage.jsx index b9dee2bd..e166578c 100644 --- a/src/Components/Pages/HomePage/HomePage.jsx +++ b/src/Components/Pages/HomePage/HomePage.jsx @@ -6,6 +6,7 @@ import Loader from '../../Loader/Loader'; import { capitalize, toReadableDate, toReadableTime } from '../../../helpers/formatHelper'; import { getLocation } from '../../../Store/selectors/geoSelectors'; import { getCurrentWeather, getLoading } from './homePageSelectors'; +import CurrentLocation from '../../CurrentLocation/CurrentLocation'; function HomePage() { const dispatch = useDispatch(); @@ -20,12 +21,8 @@ function HomePage() { return ( <>

Home

- {location && ( -

- {location.city}, {location.country} -

- )} {loading && } + {location && } {currentWeather &&

{toReadableDate(currentWeather.current.time)}

} {currentWeather &&

{toReadableTime(currentWeather.current.time)}

} {currentWeather &&

Probability of precipitation: {currentWeather.current.precipProb}%

} diff --git a/src/Store/store.js b/src/Store/store.js index c366ba19..3467e114 100644 --- a/src/Store/store.js +++ b/src/Store/store.js @@ -3,9 +3,11 @@ import thunk from 'redux-thunk'; import homePageReducer from '../Components/Pages/HomePage/homePageReducer'; import geoReducer from './reducers/geoReducer'; +import detailedForecastPageReducer from '../Components/Pages/DetailedForesastPage/detailedForecastPageReducer'; const rootReducer = combineReducers({ homePage: homePageReducer, + detailedForecastPage: detailedForecastPageReducer, geoData: geoReducer });