├── .env ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.css ├── App.js ├── App.test.js ├── components │ ├── forecast.js │ ├── styles.css │ └── weather.js ├── index.css ├── index.js ├── logo.svg ├── reportWebVitals.js └── setupTests.js └── yarn.lock /.env: -------------------------------------------------------------------------------- 1 | REACT_APP_API_URL = 'https://api.openweathermap.org/data/2.5' 2 | REACT_APP_API_KEY = 87eb04e63a1d2d86f382d92a06242e9c -------------------------------------------------------------------------------- /.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 | # React Weather App 2 | 3 | How to run: 4 | 1. Go to the root folder. 5 | 2. npm install, then npm start 6 | 7 | ## Link 8 | https://nishant-666.github.io/React-weather/ -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "homepage": "http://nishant-666.github.io/React-weather", 3 | "name": "weather-app-react", 4 | "version": "0.1.0", 5 | "private": true, 6 | "dependencies": { 7 | "@fortawesome/fontawesome-svg-core": "^1.2.34", 8 | "@fortawesome/free-solid-svg-icons": "^5.15.2", 9 | "@fortawesome/react-fontawesome": "^0.1.14", 10 | "@material-ui/core": "^4.11.3", 11 | "@testing-library/jest-dom": "^5.11.4", 12 | "@testing-library/react": "^11.1.0", 13 | "@testing-library/user-event": "^12.1.10", 14 | "axios": "^0.21.1", 15 | "gh-pages": "^3.1.0", 16 | "moment": "^2.29.1", 17 | "react": "^17.0.1", 18 | "react-dom": "^17.0.1", 19 | "react-scripts": "4.0.3", 20 | "semantic-ui-css": "^2.4.1", 21 | "semantic-ui-react": "^2.0.3", 22 | "styled-components": "^5.2.1", 23 | "web-vitals": "^1.0.1" 24 | }, 25 | "scripts": { 26 | "predeploy": "npm run build", 27 | "deploy": "gh-pages -d build", 28 | "start": "react-scripts start", 29 | "build": "react-scripts build", 30 | "test": "react-scripts test", 31 | "eject": "react-scripts eject" 32 | }, 33 | "eslintConfig": { 34 | "extends": [ 35 | "react-app", 36 | "react-app/jest" 37 | ] 38 | }, 39 | "browserslist": { 40 | "production": [ 41 | ">0.2%", 42 | "not dead", 43 | "not op_mini all" 44 | ], 45 | "development": [ 46 | "last 1 chrome version", 47 | "last 1 firefox version", 48 | "last 1 safari version" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishant-666/React-weather/ea0dc7dba6c962d08d5d96527535518af7d3a15b/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React Weather App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishant-666/React-weather/ea0dc7dba6c962d08d5d96527535518af7d3a15b/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishant-666/React-weather/ea0dc7dba6c962d08d5d96527535518af7d3a15b/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | background-color: #eeeeee; 3 | min-height: 100vh; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | justify-content: center; 8 | color: black; 9 | } 10 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import React, { useEffect, useState } from "react"; 3 | import { Dimmer, Loader } from 'semantic-ui-react'; 4 | import Weather from './components/weather'; 5 | import Forecast from './components/forecast'; 6 | export default function App() { 7 | 8 | const [lat, setLat] = useState([]); 9 | const [long, setLong] = useState([]); 10 | const [weatherData, setWeatherData] = useState([]); 11 | const [forecast, setForecast] = useState([]); 12 | const [error, setError] = useState(null); 13 | 14 | useEffect(() => { 15 | navigator.geolocation.getCurrentPosition(function(position) { 16 | setLat(position.coords.latitude); 17 | setLong(position.coords.longitude); 18 | }); 19 | 20 | getWeather(lat, long) 21 | .then(weather => { 22 | setWeatherData(weather); 23 | setError(null); 24 | }) 25 | .catch(err => { 26 | setError(err.message); 27 | }); 28 | 29 | getForecast(lat, long) 30 | .then(data => { 31 | setForecast(data); 32 | setError(null); 33 | }) 34 | .catch(err => { 35 | setError(err.message); 36 | }); 37 | 38 | }, [lat,long,error]) 39 | 40 | function handleResponse(response) { 41 | if (response.ok) { 42 | return response.json(); 43 | } else { 44 | throw new Error("Please Enable your Location in your browser!"); 45 | } 46 | } 47 | 48 | function getWeather(lat, long) { 49 | return fetch( 50 | `${process.env.REACT_APP_API_URL}/weather/?lat=${lat}&lon=${long}&units=metric&APPID=${process.env.REACT_APP_API_KEY}` 51 | ) 52 | .then(res => handleResponse(res)) 53 | .then(weather => { 54 | if (Object.entries(weather).length) { 55 | const mappedData = mapDataToWeatherInterface(weather); 56 | return mappedData; 57 | } 58 | }); 59 | } 60 | 61 | function getForecast(lat, long) { 62 | return fetch( 63 | `${process.env.REACT_APP_API_URL}/forecast/?lat=${lat}&lon=${long}&units=metric&APPID=${process.env.REACT_APP_API_KEY}` 64 | ) 65 | .then(res => handleResponse(res)) 66 | .then(forecastData => { 67 | if (Object.entries(forecastData).length) { 68 | return forecastData.list 69 | .filter(forecast => forecast.dt_txt.match(/09:00:00/)) 70 | .map(mapDataToWeatherInterface); 71 | } 72 | }); 73 | } 74 | 75 | function mapDataToWeatherInterface(data) { 76 | const mapped = { 77 | date: data.dt * 1000, // convert from seconds to milliseconds 78 | description: data.weather[0].main, 79 | temperature: Math.round(data.main.temp), 80 | }; 81 | 82 | // Add extra properties for the five day forecast: dt_txt, icon, min, max 83 | if (data.dt_txt) { 84 | mapped.dt_txt = data.dt_txt; 85 | } 86 | 87 | return mapped; 88 | } 89 | 90 | return ( 91 |
92 | {(typeof weatherData.main != 'undefined') ? ( 93 |
94 | 95 | 96 |
97 | ): ( 98 |
99 | 100 | Loading.. 101 | 102 |
103 | )} 104 |
105 | ); 106 | } 107 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/components/forecast.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | List 4 | } from "@material-ui/core"; 5 | import moment from 'moment'; 6 | import './styles.css'; 7 | import { 8 | faCloud, 9 | faBolt, 10 | faCloudRain, 11 | faCloudShowersHeavy, 12 | faSnowflake, 13 | faSun, 14 | faSmog, 15 | } from '@fortawesome/free-solid-svg-icons'; 16 | import styled from 'styled-components'; 17 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 18 | export default function Forecast(props, {weatherData}) { 19 | 20 | const WeatherIcon = styled.div` 21 | color: whitesmoke; 22 | `; 23 | 24 | const { forecast } = props; 25 | 26 | console.log("Forecast", forecast); 27 | 28 | const results = forecast.map((item, index) => { 29 | 30 | let weatherIcon = null; 31 | 32 | if (item.description === 'Thunderstorm') { 33 | weatherIcon = ; 34 | }else if (item.description === 'Drizzle') { 35 | weatherIcon = ; 36 | } else if (item.description === 'Rain') { 37 | weatherIcon = ; 38 | } else if (item.description === 'Snow') { 39 | weatherIcon = ; 40 | } else if (item.description === 'Clear') { 41 | weatherIcon = ; 42 | } else if (item.description === 'Clouds') { 43 | weatherIcon = ; 44 | } else { 45 | weatherIcon = ; 46 | } 47 | 48 | return ( 49 |
50 |
51 |

{moment(item.dt_txt).format("dddd")}

52 | 53 | {weatherIcon} 54 | 55 |

56 | {item.temperature} °C 57 |

58 |
59 |
60 | ) 61 | }) 62 | 63 | return( 64 |
65 | {results} 66 |
67 | ); 68 | 69 | } -------------------------------------------------------------------------------- /src/components/styles.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Recursive&display=swap'); 2 | 3 | .main{ 4 | margin: 10px; 5 | border-radius: 20px; 6 | background-color: #01579b; 7 | } 8 | 9 | .top{ 10 | height: 60px; 11 | background-color: #424242; 12 | color: whitesmoke; 13 | padding: 10px; 14 | border-radius: 20px 20px 0 0; 15 | font-family: 'Recursive', sans-serif; 16 | display: flex; 17 | justify-content: space-between; 18 | } 19 | 20 | .header{ 21 | background-color: #424242; 22 | color: whitesmoke; 23 | margin: 10px 0px 0px 10px; 24 | font-size: 25px; 25 | border-radius: 20px 20px 0 0; 26 | font-family: 'Recursive', sans-serif; 27 | } 28 | 29 | .button{ 30 | width: 35px; 31 | height: 35px; 32 | } 33 | 34 | .day{ 35 | padding: 15px; 36 | color: whitesmoke; 37 | font-family: 'Recursive', sans-serif; 38 | font-size: 18px; 39 | font-weight: 600; 40 | } 41 | 42 | .temp{ 43 | padding: 15px; 44 | color: whitesmoke; 45 | font-family: 'Recursive', sans-serif; 46 | font-size: 18px; 47 | } 48 | 49 | .flex{ 50 | display: flex; 51 | justify-content: space-between; 52 | } 53 | 54 | .sunrise-sunset{ 55 | padding: 15px; 56 | color: whitesmoke; 57 | font-family: 'Recursive', sans-serif; 58 | font-size: 16px; 59 | } 60 | 61 | .description{ 62 | padding: 15px; 63 | color: whitesmoke; 64 | font-family: 'Recursive', sans-serif; 65 | font-size: 24px; 66 | font-weight: 600; 67 | } 68 | 69 | .forecast{ 70 | border-radius: 20px; 71 | background-color: #01579b; 72 | color: whitesmoke; 73 | padding: 10px; 74 | margin: 10px; 75 | font-family: 'Recursive', sans-serif; 76 | font-size: 19px; 77 | } 78 | 79 | .flex-forecast{ 80 | display: flex; 81 | justify-content: space-between; 82 | } -------------------------------------------------------------------------------- /src/components/weather.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './styles.css'; 3 | import moment from 'moment'; 4 | import { Button } from 'semantic-ui-react'; 5 | import { 6 | faCloud, 7 | faBolt, 8 | faCloudRain, 9 | faCloudShowersHeavy, 10 | faSnowflake, 11 | faSun, 12 | faSmog, 13 | } from '@fortawesome/free-solid-svg-icons'; 14 | import styled from 'styled-components'; 15 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 16 | 17 | export default function weather({weatherData}) { 18 | 19 | const WeatherIcon = styled.div` 20 | color: whitesmoke; 21 | `; 22 | 23 | const refresh = () => { 24 | window.location.reload(); 25 | } 26 | 27 | let weatherIcon = null; 28 | 29 | if (weatherData.weather[0].main === 'Thunderstorm') { 30 | weatherIcon = ; 31 | } else if (weatherData.weather[0].main === 'Drizzle') { 32 | weatherIcon = ; 33 | } else if (weatherData.weather[0].main === 'Rain') { 34 | weatherIcon = ; 35 | } else if (weatherData.weather[0].main === 'Snow') { 36 | weatherIcon = ; 37 | } else if (weatherData.weather[0].main === 'Clear') { 38 | weatherIcon = ; 39 | } else if (weatherData.weather[0].main === 'Clouds') { 40 | weatherIcon = ; 41 | } else { 42 | weatherIcon = ; 43 | } 44 | 45 | return ( 46 |
47 |
48 |

{weatherData.name}

49 |
51 |
52 |

{moment().format('dddd')}, {moment().format('LL')}

53 |
54 | {weatherIcon} 55 |

{weatherData.weather[0].main}

56 |
57 |
58 | 59 |
60 |

Temprature: {weatherData.main.temp} °C

61 |

Humidity: {weatherData.main.humidity} %

62 |
63 | 64 |
65 |

Sunrise: {new Date(weatherData.sys.sunrise * 1000).toLocaleTimeString('en-IN')}

66 |

Sunset: {new Date(weatherData.sys.sunset * 1000).toLocaleTimeString('en-IN')}

67 |
68 | 69 |
70 | ) 71 | } 72 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | import 'semantic-ui-css/semantic.min.css'; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById('root') 13 | ); 14 | 15 | // If you want to start measuring performance in your app, pass a function 16 | // to log results (for example: reportWebVitals(console.log)) 17 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 18 | reportWebVitals(); 19 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | --------------------------------------------------------------------------------