Forráskód Böngészése

Created WeatherWidget

unknown 4 éve
szülő
commit
e170f56a03

+ 0 - 23
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;

+ 4 - 12
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 => {
         <p>{dateAndTime()}</p>
         <span className={classes.HeaderMain}>
           <h1>{greeter()}</h1>
-          <div className={classes.WeatherWidget}>
-            <div className={classes.WeatherIcon}>
-              <WeatherIcon icon='clear-day' />
-            </div>
-            <div className={classes.WeatherDetails}>
-              <span>30°C</span>
-              <span>15°C</span>
-            </div>
-          </div>
+          <WeatherWidget />
         </span>
       </header>
-
-      <SectionHeadline title='Apps' link='/apps' />
+      
+      <SectionHeadline title='Applications' link='/applications' />
       {props.loading
         ? <Spinner />
         : <AppGrid apps={props.apps.filter((app: App) => app.isPinned)} />

+ 340 - 0
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"
+      }
+    }
+  ]
+}

+ 22 - 0
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;
+}

+ 52 - 0
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<Weather>({
+    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<ApiResponse<Weather[]>>('/api/weather')
+      .then(data => {
+        setWeather(data.data.data[0]);
+        setIsLoading(false);
+      })
+      .catch(err => console.log(err));
+  }, []);
+
+  return (
+    <div className={classes.WeatherWidget}>
+      {isLoading
+        ? 'loading'
+        : (
+          <Fragment>
+            <div className={classes.WeatherIcon}>
+              <WeatherIcon weatherStatusCode={weather.conditionCode} />
+            </div>
+            <div className={classes.WeatherDetails}>
+              <span>{weather.tempC}°C</span>
+              <span>{weather.conditionCode}</span>
+            </div>
+          </Fragment>
+        )
+      }
+    </div>
+  )
+}
+
+export default WeatherWidget;

+ 10 - 0
client/src/interfaces/Api.ts

@@ -0,0 +1,10 @@
+export interface Model {
+  id: number;
+  createdAt: Date;
+  updatedAt: Date;
+}
+
+export interface ApiResponse<T> {
+  success: boolean;
+  data: T;
+}

+ 3 - 9
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<T> {
-  success: boolean;
-  data: T;
 }
 
 export interface NewApp {

+ 10 - 0
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;
+}

+ 3 - 1
client/src/interfaces/index.ts

@@ -1,3 +1,5 @@
 export * from './App';
 export * from './Theme';
-export * from './GlobalState';
+export * from './GlobalState';
+export * from './Api';
+export * from './Weather';

+ 5 - 5
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<T> {
   type: ActionTypes.getApps | ActionTypes.getAppsSuccess | ActionTypes.getAppsError;
@@ -15,7 +15,7 @@ export const getApps = () => async (dispatch: Dispatch) => {
   });
 
   try {
-    const res = await axios.get<AppResponse<App[]>>('/api/apps');
+    const res = await axios.get<ApiResponse<App[]>>('/api/apps');
 
     dispatch<GetAppsAction<App[]>>({
       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<AppResponse<App>>(`/api/apps/${id}`, { isPinned: !isPinned });
+    const res = await axios.put<ApiResponse<App>>(`/api/apps/${id}`, { isPinned: !isPinned });
 
     dispatch<PinAppAction>({
       type: ActionTypes.pinApp,
@@ -54,7 +54,7 @@ export interface AddAppAction {
 
 export const addApp = (formData: NewApp) => async (dispatch: Dispatch) => {
   try {
-    const res = await axios.post<AppResponse<App>>('/api/apps', formData);
+    const res = await axios.post<ApiResponse<App>>('/api/apps', formData);
 
     dispatch<AddAppAction>({
       type: ActionTypes.addAppSuccess,
@@ -72,7 +72,7 @@ export interface DeleteAppAction {
 
 export const deleteApp = (id: number) => async (dispatch: Dispatch) => {
   try {
-    const res = await axios.delete<AppResponse<{}>>(`/api/apps/${id}`);
+    const res = await axios.delete<ApiResponse<{}>>(`/api/apps/${id}`);
 
     dispatch<DeleteAppAction>({
       type: ActionTypes.deleteApp,

+ 13 - 7
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);