diff --git a/README.md b/README.md index f8b15f4cb..45d03bd82 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,26 @@ # Weather App -Replace this readme with your own information about your project. - -Start by briefly describing the assignment in a sentence or two. Keep it short and to the point. +This project is a weather application that fetches and displays weather data from the OpenWeatherMap API. The main features include displaying the current weather conditions, temperature, and a four-day forecast, as well as providing sunrise and sunset times. The assignment required following a specific design and using JavaScript to retrieve and present the data dynamically. ## The problem -Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next? +The task was to build a weather app using the OpenWeatherMap API, focusing on presenting real-time weather data such as temperature, weather description, sunrise and sunset times, and a 4-day forecast. The main challenges were to fetch the data using JavaScript's `fetch()` function, dynamically inject it into the DOM, and style the app to fit the provided design. + +#### Approach +I started by planning how to structure the application and which data points to display from the API. The first step was to set up the API connection using the provided API key and to fetch the weather data in JSON format. I used the `fetch()` method to get the data and implemented error handling with `.catch()` to handle any issues during the fetch request. + +To display the weather information, I extracted key elements from the API's JSON response, such as the city name, current temperature, weather description, and sunrise/sunset times. I used JavaScript to inject this information into the HTML elements. I also formatted the sunrise and sunset times into a more readable format using JavaScript's Date object. + +For the 4-day weather forecast, I filtered the API data to retrieve information for the same time (12:00) each day. This provided a consistent forecast and avoided overcrowding the display with too many data points. + +#### Tools and Techniques +- **JavaScript:** Used `fetch()` to call the OpenWeatherMap API and manipulate the DOM to display the weather data. +- **CSS:** Styled the app to match the provided design. I also added responsiveness to ensure it looks good on devices ranging from 320px to 1600px in width. +- **HTML:** Used semantic elements and classes to structure the content and facilitate dynamic updates with JavaScript. + +#### Additional Thoughts +If I had more time, I would have refactored the code to break out the HTML updates from the `fetch()` function into a separate function. This would have improved the code's readability and maintainability. Additionally, I would have liked to create my own design for the app to make it more personalized, as I use weather apps like SMHI frequently. Implementing more stretch goals, such as a city selection feature or dynamically changing the page's colors based on the weather conditions, would have been interesting to explore as well. ## View it live -Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about. +https://weatherapp-ek.netlify.app/ diff --git a/index.html b/index.html new file mode 100644 index 000000000..644c2db0c --- /dev/null +++ b/index.html @@ -0,0 +1,34 @@ + + + + + + + Weather app + + + + + + + +
+
+
+ +
+ +
+ +
+ +
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/pull_request_template.md b/pull_request_template.md index 70fa177f7..0a8a06744 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -1,3 +1,3 @@ ## Netlify link -Add your Netlify link here. -PS. Don't forget to add it in your readme as well. + +https://weatherapp-ek.netlify.app/ \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 000000000..9cef2c5d4 --- /dev/null +++ b/script.js @@ -0,0 +1,132 @@ +// DOM selectors +const weather = document.getElementById('weather'); +const viewCurrentWeatherIcon = document.getElementById('viewCurrentWeatherIcon'); +const viewCurrentTemp = document.getElementById('viewCurrentTemp'); +const viewCurrentCity = document.getElementById('viewCurrentCity'); +const viewCurrentWeatherText = document.getElementById('viewCurrentWeatherText'); +const viewSunriseSunset = document.getElementById('viewSunriseSunset'); +const forecastContainer = document.getElementById('forecastContainer'); + +// API +const API_KEY = '7438f6ff1587b542eeec76b91380b575'; +const city = 'Stockholm'; +const URL_WEATHER = `https://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&APPID=${API_KEY}`; +const URL_FORECAST = `https://api.openweathermap.org/data/2.5/forecast?q=${city}&units=metric&APPID=${API_KEY}`; + +const getTodaysWeather = () => { + fetch(URL_WEATHER) + // Reurn API-answere in JSON-format + .then(response => response.json()) + + .then(data => { + // Get API data + const currentWeatherIcon = data.weather[0].icon; + // Commented out this code, just to show how it looks if temperature is rounded to 1 decimal + //const currentTemp = data.main.temp.toFixed(1); + const currentTemp = Math.round(data.main.temp); + const currentCity = data.name; + const currentWeatherText = data.weather[0].description; + + // Convert the Unix timestamp returned by the OpenWeatherMap API to a readable date and time format + const sunriseTime = new Date(data.sys.sunrise * 1000); + const sunsetTime = new Date(data.sys.sunset * 1000); + + // To calculate sunset/sunrise + const currentTime = new Date(); + + // Convert time to 00:00 format + const sunriseTimeFormatted = sunriseTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + const sunsetTimeFormatted = sunsetTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + + // When background for night/day should me shown + if (currentTime >= sunriseTime && currentTime < sunsetTime) { + weather.classList.add('daytime'); + } else { + weather.classList.add('nighttime'); + } + + // Update HTML + viewCurrentWeatherIcon.innerHTML = ` + Weather icon + `; + viewCurrentTemp.innerHTML = ` +

${currentTemp}

°C

+ `; + viewCurrentCity.innerHTML = ` +

${currentCity}

+ `; + viewCurrentWeatherText.innerHTML = ` +

${currentWeatherText}

+ `; + viewSunriseSunset.innerHTML = ` +

sunrise

+

${sunriseTimeFormatted}

+

sunset

+

${sunsetTimeFormatted}

+ `; + }) + + // Catch logs and errors + .catch((error) => { + console.log('Error:', error); + }); +} + +// Call the function to get and show todays weather +getTodaysWeather(); + +const getWeatherForecast = () => { + fetch(URL_FORECAST) + .then(response => response.json()) + + .then(data => { + // Get unique days in the forecast (filtering by date without time)" + const days = {}; + data.list.forEach(item => { + const date = item.dt_txt.split(' ')[0]; // Get only date + if (!days[date]) { + days[date] = []; // Create array for each date + } + days[date].push(item); // Add all weather entries for each date + }); + + // Skip first day (index 0) + const filteredDays = Object.keys(days).slice(1, 6); + + // Loop through filtred days and show the data + filteredDays.forEach(date => { + const dayForecast = days[date]; + + // Find highest and lowest temperature for each day + const temps = dayForecast.map(item => Math.round(item.main.temp)); + const maxTemp = Math.max(...temps); + const minTemp = Math.min(...temps); + + // Get weahter icon at 12:00 for the specific day + const middayForecast = dayForecast.find(item => item.dt_txt.includes('12:00:00')); + const weatherIcon = middayForecast ? middayForecast.weather[0].icon : dayForecast[0].weather[0].icon; + + // Get the weekday (three letters) + const dayOfWeek = new Date(date).toLocaleDateString('en-GB', { weekday: 'short' }); + + // Build HTML for every days forecast + forecastContainer.innerHTML += ` +
+

${dayOfWeek}

+
+ Weather icon +

${minTemp}° / ${maxTemp}°C

+
+
+ `; + }); + }) + + // Catch logs and errors + .catch(error => { + console.error('Error:', error); + }); +} + +// Call the function to get and show weather forecast +getWeatherForecast(); \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 000000000..9a3aa97f8 --- /dev/null +++ b/style.css @@ -0,0 +1,157 @@ +body { + background-color: white; + font-family: "Roboto", sans-serif; + font-weight: 300; + font-style: normal; + margin: 0; +} + +h1 { + font-size: 120px; + margin: 0; +} + +h2 { + font-size: 40px; + font-weight: 400; + margin-top: 15px; +} + +h3 { + font-size: 34px; + margin: 0; +} + +h4 { + font-size: 22px; + margin: 0; +} + +p { + color: #707070; + font-size: 20px; + font-weight: 400; +} + +.weather-container { + color: white; + margin: 0 30px; + position: relative; +} + +/* Day background styling (lightblue) */ +.daytime { + background: linear-gradient(to bottom right, #8589FF, #E8E9FF); + clip-path: ellipse(130% 100% at 50% 0%); +} + +/* Night background styling (darkblue) */ +.nighttime { + background: linear-gradient(to bottom right, #6264A2, #222350); + clip-path: ellipse(130% 100% at 50% 0%); +} + +/* Position of big weather icon */ +.view-current-weather-icon { + position: absolute; + right: 70px; +} + +/* Icon size */ +.view-current-weather-icon img { + width: 200%; +} + +.view-current-temp { + padding-top: 131px; + display: flex; + flex-direction: row; +} + +/* View weather text with capitals */ +.view-current-weather-text { + text-transform: capitalize; +} + +.view-sunrise-sunset { + display: flex; + flex-direction: row; + justify-content: space-between; + padding: 10% 0 15% 0; +} + +.forecast-container { + margin: 0 30px; + margin-top: 20%; + margin-bottom: 10%; +} + +.forecast { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +/* Styling for icon and temperature to be to the right */ +.forecast-icon-temp { + display: flex; + flex-direction: row; + justify-content: space-between; + width: 50%; +} + +/* Make icon not streach */ +.forecast-icon-temp img { + object-fit: contain; +} + +/* For tablets */ +@media (min-width: 668px) and (max-width: 1024px) { + + .weather-container { + margin: 0 100px; + } + + .view-current-weather-icon { + right: 100px; + } + + .view-current-weather-icon img { + width: 300%; + + } + + .forecast-container { + margin: 100px 100px; + } + + .forecast { + margin: 3% 0; + } +} + +/* For screens */ +@media (min-width: 1025px) { + + body { + display: flex; + justify-content: space-between; + } + + .weather-container { + margin: 0 100px; + } + + .daytime, + .nighttime { + width: 50%; + clip-path: none; + height: 100vh; + } + + .forecast-container { + width: 50%; + margin: 131px 100px; + } + +} \ No newline at end of file