├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── manifest.json └── robots.txt ├── screenshot.png └── src ├── Api.js ├── App.css ├── App.js ├── assets ├── bg.png ├── humidity.svg ├── pressure.svg ├── sunrise.svg ├── sunset.svg └── wind_speed.svg ├── components ├── Loader.css ├── Loader.js ├── LocalInfo.js ├── Search.css ├── Search.js ├── Today.js ├── Weather.js └── Weekly.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Screenshot](screenshot.png) 2 | 3 | --- 4 | 5 | # React Weather Application 6 | ## Find the current weather and 7 days forecast of any city on earth with this simple little web app. 7 | 8 | Live Demo on [Netlify](https://monitor-weather.netlify.app). 9 | 10 | --- 11 | 12 | Simple React web application written with jsx that returns the current weather. The app utilizes: 13 | 14 | - OpenWeatherMap's API, 15 | - OpenWeatherMap's icons for weather icons, 16 | - create-react-app tool, 17 | - Axios for easy http requests, 18 | - Material-ui for styling and design. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "weather-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.11.0", 7 | "@testing-library/jest-dom": "^4.2.4", 8 | "@testing-library/react": "^9.5.0", 9 | "@testing-library/user-event": "^7.2.1", 10 | "axios": "^0.20.0", 11 | "react": "^17.0.1", 12 | "react-dom": "^17.0.1", 13 | "react-scripts": "3.4.4" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject" 20 | }, 21 | "eslintConfig": { 22 | "extends": "react-app" 23 | }, 24 | "browserslist": { 25 | "production": [ 26 | ">0.2%", 27 | "not dead", 28 | "not op_mini all" 29 | ], 30 | "development": [ 31 | "last 1 chrome version", 32 | "last 1 firefox version", 33 | "last 1 safari version" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fantasy1114/weather_react_app/051edad97c38333c27a5e1974a4e8b086155722d/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 17 | 18 | 27 | Weather App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Weather App", 3 | "name": "React Weather App", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fantasy1114/weather_react_app/051edad97c38333c27a5e1974a4e8b086155722d/screenshot.png -------------------------------------------------------------------------------- /src/Api.js: -------------------------------------------------------------------------------- 1 | const API_KEY = 'd94bcd435b62a031771c35633f9f310a' 2 | const URL = "https://api.openweathermap.org/data/2.5/forecast/daily" 3 | 4 | // export const coordinates = (location) => `${URL}weather?q=${location}&appid=${API_KEY}` 5 | 6 | export const weatherForecast = (loc) => `${URL}?q=${loc}&units=metric&cnt=7&appid=${API_KEY}` 7 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | *{ 2 | box-sizing: border-box; 3 | padding: 0; 4 | margin: 0; 5 | 6 | } 7 | 8 | .error__loc { 9 | font-size: x-large; 10 | color: rgb(0, 26, 255); 11 | font-weight: bold; 12 | position: fixed; 13 | top: 50%; 14 | left: 50%; 15 | transform: translate(-50%, -50%); 16 | } 17 | body{ 18 | min-height: 100vh; 19 | position: relative; 20 | height: 100%; 21 | width: 100%; 22 | background-image: url('./assets/bg.png'); 23 | background-repeat: no-repeat; 24 | background-position: center; 25 | background-size: cover; 26 | } 27 | 28 | body::-webkit-scrollbar { 29 | display: none; 30 | } 31 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import './App.css'; 3 | import axios from "axios"; 4 | import Search from './components/Search'; 5 | import {weatherForecast} from './Api' 6 | import Weather from './components/Weather'; 7 | import Loader from './components/Loader'; 8 | 9 | function App() { 10 | const [state, setState] = useState({ 11 | value: '', 12 | current: { 13 | }, 14 | weekInfo: [], 15 | loading: false, 16 | error: false, 17 | }) 18 | 19 | const handleInputChange = e => { 20 | setState({ 21 | ...state, 22 | value: e.target.value, 23 | }) 24 | }; 25 | 26 | const handleSearchCity = e => { 27 | e.preventDefault(); 28 | setState({ 29 | ...state, 30 | loading: true, 31 | }) 32 | axios.get(weatherForecast(state.value)) 33 | .then(response => { 34 | const data = response.data 35 | const months = [ 36 | 'January', 37 | 'February', 38 | 'March', 39 | 'April', 40 | 'May', 41 | 'June', 42 | 'July', 43 | 'August', 44 | 'September', 45 | 'October', 46 | 'Nocvember', 47 | 'December', 48 | ] 49 | 50 | const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] 51 | const currentDate = new Date() 52 | const date = `${days[currentDate.getDay()]} ${currentDate.getDate()} ${ 53 | months[currentDate.getMonth()] 54 | }`; 55 | 56 | const sunset = new Date(data.list[0].sunset * 1000).toLocaleTimeString().slice(0, 4) 57 | const sunrise = new Date(data.list[0].sunrise * 1000).toLocaleTimeString().slice(0, 4) 58 | 59 | const current = { 60 | city: data.city.name, 61 | country: data.city.country, 62 | date, 63 | population: data.city.population, 64 | desc: data.list[0].weather[0].description, 65 | main: data.list[0].weather[0].main, 66 | icon: data.list[0].weather[0].icon, 67 | temp: data.list[0].temp.day, 68 | hTemp: data.list[0].temp.max, 69 | lTemp: data.list[0].temp.min, 70 | sunrise, 71 | sunset, 72 | clouds: data.list[0].clouds, 73 | humidity: data.list[0].humidity, 74 | wind: data.list[0].speed, 75 | pressure: data.list[0].pressure, 76 | } 77 | 78 | const weekData = data.list 79 | const weekInfo = weekData.map((data, index) => { 80 | return{ 81 | key:index, 82 | main: data.weather[0].main, 83 | day: new Date(data.dt * 1000).toLocaleString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }).slice(0,3), 84 | desc: data.weather[0].description, 85 | icon: data.weather[0].icon, 86 | hTemp: data.temp.max, 87 | lTemp: data.temp.min, 88 | }}) 89 | 90 | setState({ 91 | ...state, 92 | current, 93 | weekInfo, 94 | loading: false, 95 | error: false, 96 | }) 97 | 98 | }) 99 | .catch(error => { 100 | console.log(error); 101 | 102 | setState({ 103 | ...state, 104 | loading: false, 105 | error: true, 106 | current: {}, 107 | weekInfo: [], 108 | }) 109 | }) 110 | } 111 | 112 | return ( 113 | <> 114 | 121 | { 122 | state.loading === true ? 123 | : 124 |
125 | {state.current.country !== undefined ? 126 |
127 | 128 |
: 129 | state.error ? 130 |

Sorry! we donot have any information on specified location.

: 131 |
132 | 133 |
134 | } 135 |
136 | } 137 | 138 | ) 139 | } 140 | 141 | export default App; 142 | -------------------------------------------------------------------------------- /src/assets/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fantasy1114/weather_react_app/051edad97c38333c27a5e1974a4e8b086155722d/src/assets/bg.png -------------------------------------------------------------------------------- /src/assets/humidity.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/assets/pressure.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 17 | 19 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/assets/sunrise.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/sunset.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 21 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/assets/wind_speed.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Loader.css: -------------------------------------------------------------------------------- 1 | .lds-ripple { 2 | display: inline-block; 3 | width: 80px; 4 | height: 80px; 5 | position: fixed; 6 | top: 50%; 7 | left: 50%; 8 | transform: translate(-50%, -50%); 9 | } 10 | .lds-ripple div { 11 | position: absolute; 12 | border: 4px solid #fff; 13 | opacity: 1; 14 | border-radius: 50%; 15 | animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite; 16 | } 17 | .lds-ripple div:nth-child(2) { 18 | animation-delay: -0.5s; 19 | } 20 | @keyframes lds-ripple { 21 | 0% { 22 | top: 36px; 23 | left: 36px; 24 | width: 0; 25 | height: 0; 26 | opacity: 1; 27 | } 28 | 100% { 29 | top: 0px; 30 | left: 0px; 31 | width: 72px; 32 | height: 72px; 33 | opacity: 0; 34 | } 35 | } -------------------------------------------------------------------------------- /src/components/Loader.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './Loader.css' 3 | 4 | function Loader() { 5 | return ( 6 |
7 |
8 |
9 |
10 | ) 11 | } 12 | 13 | export default Loader 14 | -------------------------------------------------------------------------------- /src/components/LocalInfo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Typography from '@material-ui/core/Typography'; 3 | import { CardMedia } from '@material-ui/core'; 4 | 5 | function LocalInfo({today: { city, country, date, population}}) { 6 | return ( 7 | 8 | 9 | {city}, {country} 10 | 11 | 12 | {date} 13 | 14 | 15 | Population: {population.toLocaleString()} 16 | 17 | 18 | ) 19 | } 20 | 21 | export default LocalInfo 22 | -------------------------------------------------------------------------------- /src/components/Search.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Raleway:400,700,900'); 2 | 3 | 4 | 5 | .search__container { 6 | padding-top: 64px; 7 | width: 400px; 8 | margin: 0 auto; 9 | background-color: transparent; 10 | font-family: 'Raleway', sans-serif; 11 | } 12 | 13 | 14 | .search__input { 15 | width: 100%; 16 | padding: 12px 24px; 17 | background-color: transparent; 18 | transition: transform 250ms ease-in-out; 19 | font-size: 14px; 20 | line-height: 18px; 21 | color: #000000; 22 | background-color: transparent; 23 | background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); 24 | background-repeat: no-repeat; 25 | background-size: 18px 18px; 26 | background-position: 95% center; 27 | border-radius: 50px; 28 | border: 1px solid #ffffff; 29 | transition: all 250ms ease-in-out; 30 | backface-visibility: hidden; 31 | transform-style: preserve-3d; 32 | } 33 | 34 | .search__input::placeholder { 35 | color: #1f1d1d; 36 | letter-spacing: 1.5px; 37 | } 38 | 39 | .search__input:hover, 40 | .search__input:focus { 41 | padding: 12px 0; 42 | outline: 0; 43 | border: 2px solid transparent; 44 | border-bottom: 2px solid #0003c5; 45 | border-radius: 0; 46 | background-position: 100% center; 47 | } -------------------------------------------------------------------------------- /src/components/Search.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import "./Search.css" 3 | 4 | 5 | function Search({value, data, change, submit}) { 6 | return ( 7 | <> 8 |
9 | 10 |
11 | 12 | ) 13 | } 14 | 15 | export default Search 16 | -------------------------------------------------------------------------------- /src/components/Today.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import Typography from '@material-ui/core/Typography'; 4 | import pressure from '../assets/pressure.svg' 5 | import wind_speed from '../assets/wind_speed.svg' 6 | import humidity from '../assets/humidity.svg' 7 | import sunrise from '../assets/sunrise.svg' 8 | import sunset from '../assets/sunset.svg' 9 | import CardContent from '@material-ui/core/CardContent'; 10 | 11 | 12 | const useStyles = makeStyles((theme) => ({ 13 | unit__icon: { 14 | width: 22, 15 | height:22, 16 | alignSelf: 'center', 17 | marginRight: 4, 18 | marginLeft: 20, 19 | }, 20 | unit__icon1: { 21 | width: 22, 22 | height:22, 23 | alignSelf: 'center', 24 | fontSize: '15', 25 | }, 26 | weather__icon: { 27 | width: 90, 28 | height: 90, 29 | Top: 0, 30 | }, 31 | main : { 32 | overflow: 'auto', 33 | padding: 5, 34 | }, 35 | text__left: { 36 | float: 'left', 37 | }, 38 | text__right: { 39 | float: 'right', 40 | }, 41 | span: { 42 | fontWeight: 'bold', 43 | } 44 | 45 | })); 46 | 47 | function Today({today}) { 48 | const classes = useStyles(); 49 | return ( 50 | 51 |
52 |
53 | {today.icon} 54 | 55 | {today.temp}°C 56 | 57 | 58 | {today.main}, {today.desc} 59 | 60 |
61 |
62 | 63 | 64 | Logo {today.sunrise} A.M. 65 | 66 | 67 | Logo {today.sunset} P.M. 68 | 69 | 70 |
71 |
72 |
73 | Logo{today.pressure} hPa 74 | Logo{today.humidity} % 75 | Logo{today.wind} m/s N 76 |
77 |
78 | ) 79 | } 80 | 81 | export default Today 82 | -------------------------------------------------------------------------------- /src/components/Weather.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import Card from '@material-ui/core/Card'; 4 | import Grid from '@material-ui/core/Grid'; 5 | import LocalInfo from './LocalInfo'; 6 | import Today from './Today'; 7 | import Weekly from './Weekly' 8 | 9 | 10 | const useStyles = makeStyles((theme) => ({ 11 | root: { 12 | flexGrow: 1, 13 | marginTop: 60, 14 | padding: 15, 15 | 16 | }, 17 | card: { 18 | padding: theme.spacing(2), 19 | }, 20 | section: { 21 | height: "100%", 22 | paddingTop: 5, 23 | backgroundColor: 'rgba(5,4,2,0.1)', 24 | }, 25 | })); 26 | 27 | function Weather({today, weekly}) { 28 | const classes = useStyles(); 29 | 30 | return ( 31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 | ) 51 | } 52 | export default Weather -------------------------------------------------------------------------------- /src/components/Weekly.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import GridList from '@material-ui/core/GridList'; 4 | import GridListTile from '@material-ui/core/GridListTile'; 5 | import Typography from '@material-ui/core/Typography'; 6 | import { CardMedia } from '@material-ui/core'; 7 | 8 | 9 | const useStyles = makeStyles((theme) => ({ 10 | root: { 11 | marginTop: '20px', 12 | display: 'flex', 13 | flexWrap: 'wrap', 14 | justifyContent: 'space-around', 15 | overflow: 'auto', 16 | }, 17 | gridList: { 18 | flexWrap: 'nowrap', 19 | alignContent: 'center', 20 | }, 21 | weather__icon: { 22 | width: 60, 23 | height: 60, 24 | top: 0, 25 | transform: 'translateY(0%)', 26 | left: 0, 27 | }, 28 | day: { 29 | textAlign: 'center', 30 | border: '1px solid', 31 | minWidth: '185px', 32 | }, 33 | info:{ 34 | fontSize: 21, 35 | fontWeight: "bold", 36 | } 37 | })); 38 | 39 | function Weekly({weekData}) { 40 | const classes = useStyles(); 41 | 42 | return ( 43 | 44 | 45 | {weekData.map((data) => ( 46 | 47 | { (data.key === 0) ? 48 | 49 | Today 50 | : 51 | 52 | {data.day} 53 | 54 | } 55 | 56 | {data.icon} 61 | 62 | {data.lTemp}°C - {data.hTemp}°C 63 | 64 | 65 | {data.main}, 66 | 67 |

{data.desc}

68 |
69 | ))} 70 |
71 |
72 | ); 73 | } 74 | 75 | export default Weekly; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('root') 9 | ); 10 | 11 | --------------------------------------------------------------------------------