Created WeatherWidget
This commit is contained in:
parent
38f5d3b66a
commit
e170f56a03
11 changed files with 462 additions and 57 deletions
|
@ -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;
|
||||
|
|
|
@ -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
client/src/components/UI/Icons/WeatherIcon/WeatherMapping.json
Normal file
340
client/src/components/UI/Icons/WeatherIcon/WeatherMapping.json
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
client/src/interfaces/Api.ts
Normal file
10
client/src/interfaces/Api.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
export interface Model {
|
||||
id: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface ApiResponse<T> {
|
||||
success: boolean;
|
||||
data: T;
|
||||
}
|
|
@ -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
client/src/interfaces/Weather.ts
Normal file
10
client/src/interfaces/Weather.ts
Normal file
|
@ -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;
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
export * from './App';
|
||||
export * from './Theme';
|
||||
export * from './GlobalState';
|
||||
export * from './GlobalState';
|
||||
export * from './Api';
|
||||
export * from './Weather';
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue