├── .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 | 
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 |
57 |
--------------------------------------------------------------------------------
/src/assets/pressure.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
57 |
--------------------------------------------------------------------------------
/src/assets/sunrise.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/sunset.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
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 |
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 |

54 |
55 | {today.temp}°C
56 |
57 |
58 | {today.main}, {today.desc}
59 |
60 |
61 |
62 |
63 |
64 |
{today.sunrise} A.M.
65 |
66 |
67 |
{today.sunset} P.M.
68 |
69 |
70 |
71 |
72 |
73 |
{today.pressure} hPa
74 |
{today.humidity} %
75 |
{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 |
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 |
--------------------------------------------------------------------------------