From e170f56a038b2be340113e45d40ce78e6f78ccaf Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 19 May 2021 18:26:57 +0200 Subject: [PATCH] Created WeatherWidget --- client/src/components/Home/Home.module.css | 23 -- client/src/components/Home/Home.tsx | 16 +- .../UI/Icons/WeatherIcon/WeatherMapping.json | 340 ++++++++++++++++++ .../WeatherWidget/WeatherWidget.module.css | 22 ++ .../Widgets/WeatherWidget/WeatherWidget.tsx | 52 +++ client/src/interfaces/Api.ts | 10 + client/src/interfaces/App.ts | 12 +- client/src/interfaces/Weather.ts | 10 + client/src/interfaces/index.ts | 4 +- client/src/store/actions/app.ts | 10 +- utils/getExternalWeather.js | 20 +- 11 files changed, 462 insertions(+), 57 deletions(-) create mode 100644 client/src/components/UI/Icons/WeatherIcon/WeatherMapping.json create mode 100644 client/src/components/Widgets/WeatherWidget/WeatherWidget.module.css create mode 100644 client/src/components/Widgets/WeatherWidget/WeatherWidget.tsx create mode 100644 client/src/interfaces/Api.ts create mode 100644 client/src/interfaces/Weather.ts diff --git a/client/src/components/Home/Home.module.css b/client/src/components/Home/Home.module.css index 30ac2bb..09da7c5 100644 --- a/client/src/components/Home/Home.module.css +++ b/client/src/components/Home/Home.module.css @@ -19,29 +19,6 @@ margin-bottom: 2.5rem; } -.WeatherWidget { - display: flex; -} - -.WeatherDetails { - display: flex; - flex-direction: column; - justify-content: center; - font-size: 16px; - color: var(--color-primary); - margin-left: 10px; - font-weight: 500; -} - -.WeatherDetails span:first-child { - border-bottom: 1px solid var(--color-primary); - padding-bottom: 5px; -} - -.WeatherDetails span:last-child { - padding-top: 5px; -} - .SettingsButton { width: 35px; height: 35px; diff --git a/client/src/components/Home/Home.tsx b/client/src/components/Home/Home.tsx index 6a928b7..5021a38 100644 --- a/client/src/components/Home/Home.tsx +++ b/client/src/components/Home/Home.tsx @@ -5,7 +5,6 @@ import { GlobalState } from '../../interfaces/GlobalState'; import { getApps } from '../../store/actions'; import Icon from '../UI/Icons/Icon/Icon'; -import WeatherIcon from '../UI/Icons/WeatherIcon/WeatherIcon'; import classes from './Home.module.css'; import { Container } from '../UI/Layout/Layout'; @@ -13,6 +12,7 @@ import SectionHeadline from '../UI/Headlines/SectionHeadline/SectionHeadline'; import AppGrid from '../Apps/AppGrid/AppGrid'; import { App } from '../../interfaces'; import Spinner from '../UI/Spinner/Spinner'; +import WeatherWidget from '../Widgets/WeatherWidget/WeatherWidget'; interface ComponentProps { getApps: Function; @@ -63,19 +63,11 @@ const Home = (props: ComponentProps): JSX.Element => {

{dateAndTime()}

{greeter()}

-
-
- -
-
- 30°C - 15°C -
-
+
- - + + {props.loading ? : app.isPinned)} /> diff --git a/client/src/components/UI/Icons/WeatherIcon/WeatherMapping.json b/client/src/components/UI/Icons/WeatherIcon/WeatherMapping.json new file mode 100644 index 0000000..6a7bdaa --- /dev/null +++ b/client/src/components/UI/Icons/WeatherIcon/WeatherMapping.json @@ -0,0 +1,340 @@ +{ + "mapping": [ + { + "code": 1000, + "icon": { + "day": "clear-day", + "night": "clear-night" + } + }, + { + "code": 1003, + "icon": { + "day": "partly-cloudy-day", + "night": "partly-cloudy-night" + } + }, + { + "code": 1006, + "icon": { + "day": "cloudy", + "night": "cloudy" + } + }, + { + "code": 1009, + "icon": { + "day": "cloudy", + "night": "cloudy" + } + }, + { + "code": 1030, + "icon": { + "day": "fog", + "night": "fog" + } + }, + { + "code": 1063, + "icon": { + "day": "rain-day", + "night": "rain-night" + } + }, + { + "code": 1066, + "icon": { + "day": "snow-day", + "night": "snow-night" + } + }, + { + "code": 1069, + "icon": { + "day": "rain-snow-day", + "night": "rain-snow-night" + } + }, + { + "code": 1072, + "icon": { + "day": "sleet", + "night": "sleet" + } + }, + { + "code": 1087, + "icon": { + "day": "thunder-day", + "night": "thunder-night" + } + }, + { + "code": 1114, + "icon": { + "day": "snow", + "night": "snow" + } + }, + { + "code": 1117, + "icon": { + "day": "snow", + "night": "snow" + } + }, + { + "code": 1135, + "icon": { + "day": "fog", + "night": "fog" + } + }, + { + "code": 1147, + "icon": { + "day": "fog", + "night": "fog" + } + }, + { + "code": 1150, + "icon": { + "day": "rain", + "night": "rain" + } + }, + { + "code": 1153, + "icon": { + "day": "rain", + "night": "rain" + } + }, + { + "code": 1168, + "icon": { + "day": "sleet", + "night": "sleet" + } + }, + { + "code": 1171, + "icon": { + "day": "sleet", + "night": "sleet" + } + }, + { + "code": 1180, + "icon": { + "day": "rain-day", + "night": "rain-night" + } + }, + { + "code": 1183, + "icon": { + "day": "rain", + "night": "rain" + } + }, + { + "code": 1186, + "icon": { + "day": "rain-day", + "night": "rain-night" + } + }, + { + "code": 1189, + "icon": { + "day": "rain", + "night": "rain" + } + }, + { + "code": 1192, + "icon": { + "day": "rain-day", + "night": "rain-night" + } + }, + { + "code": 1195, + "icon": { + "day": "rain", + "night": "rain" + } + }, + { + "code": 1198, + "icon": { + "day": "sleet", + "night": "sleet" + } + }, + { + "code": 1201, + "icon": { + "day": "sleet", + "night": "sleet" + } + }, + { + "code": 1204, + "icon": { + "day": "rain-snow", + "night": "rain-snow" + } + }, + { + "code": 1207, + "icon": { + "day": "rain-snow", + "night": "rain-snow" + } + }, + { + "code": 1210, + "icon": { + "day": "snow-day", + "night": "snow-night" + } + }, + { + "code": 1213, + "icon": { + "day": "snow", + "night": "snow" + } + }, + { + "code": 1216, + "icon": { + "day": "snow-day", + "night": "snow-night" + } + }, + { + "code": 1219, + "icon": { + "day": "snow", + "night": "snow" + } + }, + { + "code": 1222, + "icon": { + "day": "snow-day", + "night": "snow-night" + } + }, + { + "code": 1225, + "icon": { + "day": "snow", + "night": "snow" + } + }, + { + "code": 1237, + "icon": { + "day": "hail", + "night": "hail" + } + }, + { + "code": 1240, + "icon": { + "day": "rain-day", + "night": "rain-night" + } + }, + { + "code": 1243, + "icon": { + "day": "rain-day", + "night": "rain-night" + } + }, + { + "code": 1246, + "icon": { + "day": "rain-day", + "night": "rain-night" + } + }, + { + "code": 1249, + "icon": { + "day": "rain-snow-day", + "night": "rain-snow-night" + } + }, + { + "code": 1252, + "icon": { + "day": "rain-snow-day", + "night": "rain-snow-night" + } + }, + { + "code": 1255, + "icon": { + "day": "snow-day", + "night": "snow-night" + } + }, + { + "code": 1258, + "icon": { + "day": "snow-day", + "night": "snow-night" + } + }, + { + "code": 1261, + "icon": { + "day": "hail", + "night": "hail" + } + }, + { + "code": 1264, + "icon": { + "day": "hail", + "night": "hail" + } + }, + { + "code": 1273, + "icon": { + "day": "thunder-rain-day", + "night": "thunder-rain-night" + } + }, + { + "code": 1276, + "icon": { + "day": "thunder-rain", + "night": "thunder-rain" + } + }, + { + "code": 1279, + "icon": { + "day": "thunder-day", + "night": "thunder-night" + } + }, + { + "code": 1282, + "icon": { + "day": "thunder", + "night": "thunder" + } + } + ] +} \ No newline at end of file diff --git a/client/src/components/Widgets/WeatherWidget/WeatherWidget.module.css b/client/src/components/Widgets/WeatherWidget/WeatherWidget.module.css new file mode 100644 index 0000000..e13f5ab --- /dev/null +++ b/client/src/components/Widgets/WeatherWidget/WeatherWidget.module.css @@ -0,0 +1,22 @@ +.WeatherWidget { + display: flex; +} + +.WeatherDetails { + display: flex; + flex-direction: column; + justify-content: center; + font-size: 16px; + color: var(--color-primary); + margin-left: 10px; + font-weight: 500; +} + +.WeatherDetails span:first-child { + border-bottom: 1px solid var(--color-primary); + padding-bottom: 5px; +} + +.WeatherDetails span:last-child { + padding-top: 5px; +} \ No newline at end of file diff --git a/client/src/components/Widgets/WeatherWidget/WeatherWidget.tsx b/client/src/components/Widgets/WeatherWidget/WeatherWidget.tsx new file mode 100644 index 0000000..c3cf36b --- /dev/null +++ b/client/src/components/Widgets/WeatherWidget/WeatherWidget.tsx @@ -0,0 +1,52 @@ +import { useState, useEffect, Fragment } from 'react'; +import { Weather, ApiResponse } from '../../../interfaces'; +import axios from 'axios'; + +import WeatherIcon from '../../UI/Icons/WeatherIcon/WeatherIcon'; + +import classes from './WeatherWidget.module.css'; + +const WeatherWidget = (): JSX.Element => { + const [weather, setWeather] = useState({ + externalLastUpdate: '', + tempC: 0, + tempF: 0, + isDay: 1, + conditionText: '', + conditionCode: 1000, + id: 0, + createdAt: new Date(), + updatedAt: new Date() + }); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + axios.get>('/api/weather') + .then(data => { + setWeather(data.data.data[0]); + setIsLoading(false); + }) + .catch(err => console.log(err)); + }, []); + + return ( +
+ {isLoading + ? 'loading' + : ( + +
+ +
+
+ {weather.tempC}°C + {weather.conditionCode} +
+
+ ) + } +
+ ) +} + +export default WeatherWidget; \ No newline at end of file diff --git a/client/src/interfaces/Api.ts b/client/src/interfaces/Api.ts new file mode 100644 index 0000000..2174598 --- /dev/null +++ b/client/src/interfaces/Api.ts @@ -0,0 +1,10 @@ +export interface Model { + id: number; + createdAt: Date; + updatedAt: Date; +} + +export interface ApiResponse { + success: boolean; + data: T; +} \ No newline at end of file diff --git a/client/src/interfaces/App.ts b/client/src/interfaces/App.ts index ffdbf82..1904ea0 100644 --- a/client/src/interfaces/App.ts +++ b/client/src/interfaces/App.ts @@ -1,16 +1,10 @@ -export interface App { - id: number; +import { Model } from './Api'; + +export interface App extends Model { name: string; url: string; icon: string; isPinned: boolean; - createdAt: Date; - updatedAt: Date; -} - -export interface AppResponse { - success: boolean; - data: T; } export interface NewApp { diff --git a/client/src/interfaces/Weather.ts b/client/src/interfaces/Weather.ts new file mode 100644 index 0000000..ea8e0d1 --- /dev/null +++ b/client/src/interfaces/Weather.ts @@ -0,0 +1,10 @@ +import { Model } from './Api'; + +export interface Weather extends Model { + externalLastUpdate: string; + tempC: number; + tempF: number; + isDay: number; + conditionText: string; + conditionCode: number; +} \ No newline at end of file diff --git a/client/src/interfaces/index.ts b/client/src/interfaces/index.ts index 516fda3..5b42fe8 100644 --- a/client/src/interfaces/index.ts +++ b/client/src/interfaces/index.ts @@ -1,3 +1,5 @@ export * from './App'; export * from './Theme'; -export * from './GlobalState'; \ No newline at end of file +export * from './GlobalState'; +export * from './Api'; +export * from './Weather'; \ No newline at end of file diff --git a/client/src/store/actions/app.ts b/client/src/store/actions/app.ts index 74fb079..9732a39 100644 --- a/client/src/store/actions/app.ts +++ b/client/src/store/actions/app.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { Dispatch } from 'redux'; import { ActionTypes } from './actionTypes'; -import { App, AppResponse, NewApp } from '../../interfaces/App'; +import { App, ApiResponse, NewApp } from '../../interfaces'; export interface GetAppsAction { type: ActionTypes.getApps | ActionTypes.getAppsSuccess | ActionTypes.getAppsError; @@ -15,7 +15,7 @@ export const getApps = () => async (dispatch: Dispatch) => { }); try { - const res = await axios.get>('/api/apps'); + const res = await axios.get>('/api/apps'); dispatch>({ type: ActionTypes.getAppsSuccess, @@ -36,7 +36,7 @@ export interface PinAppAction { export const pinApp = (id: number, isPinned: boolean) => async (dispatch: Dispatch) => { try { - const res = await axios.put>(`/api/apps/${id}`, { isPinned: !isPinned }); + const res = await axios.put>(`/api/apps/${id}`, { isPinned: !isPinned }); dispatch({ type: ActionTypes.pinApp, @@ -54,7 +54,7 @@ export interface AddAppAction { export const addApp = (formData: NewApp) => async (dispatch: Dispatch) => { try { - const res = await axios.post>('/api/apps', formData); + const res = await axios.post>('/api/apps', formData); dispatch({ type: ActionTypes.addAppSuccess, @@ -72,7 +72,7 @@ export interface DeleteAppAction { export const deleteApp = (id: number) => async (dispatch: Dispatch) => { try { - const res = await axios.delete>(`/api/apps/${id}`); + const res = await axios.delete>(`/api/apps/${id}`); dispatch({ type: ActionTypes.deleteApp, diff --git a/utils/getExternalWeather.js b/utils/getExternalWeather.js index f265e20..b36d825 100644 --- a/utils/getExternalWeather.js +++ b/utils/getExternalWeather.js @@ -3,21 +3,27 @@ const Weather = require('../models/Weather'); const axios = require('axios'); const getExternalWeather = async () => { - // Get API key from database - let secret = await Config.findOne({ - where: { key: 'WEATHER_API_KEY' } - }); + // Get config from database + const config = await Config.findAll(); + + // Find and check values + const secret = config.find(pair => pair.key === 'WEATHER_API_KEY'); + const lat = config.find(pair => pair.key === 'lat'); + const long = config.find(pair => pair.key === 'long'); if (!secret) { - console.log('API key was not found'); + console.log('API key was not found. Weather updated failed'); return; } - secret = secret.value; + if (!lat || !long) { + console.log('Location was not found. Weather updated failed'); + return; + } // Fetch data from external API try { - const res = await axios.get(`http://api.weatherapi.com/v1/current.json?key=${secret}&q=52.229676,21.012229`); + const res = await axios.get(`http://api.weatherapi.com/v1/current.json?key=${secret.value}&q=${lat.value},${long.value}`); // For dev // console.log(res.data);