diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..c32883842 Binary files /dev/null and b/.DS_Store differ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..6f3a2913e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "liveServer.settings.port": 5501 +} \ No newline at end of file diff --git a/README.md b/README.md index f8b15f4cb..f439760ca 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,35 @@ # Weather App +This is a dynamic weather app built using JavaScript that allows users to search for the current weather and a 4-day weather forecast for any city. The app fetches weather data from the OpenWeatherMap API and displays the current temperature, weather description, sunrise and sunset times, as well as a 4-day forecast with daily minimum and maximum temperatures. The app also adjusts its theme to display a day or night background based on the current time at the selected location. -Replace this readme with your own information about your project. +# Features +Search Weather by City: Users can search for the weather by entering a city name. -Start by briefly describing the assignment in a sentence or two. Keep it short and to the point. +Current Weather: Displays the current temperature, weather conditions, sunrise and sunset times. -## The problem +4-Day Forecast: Shows a 4-day weather forecast, including minimum and maximum temperatures for each day. -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? +Day/Night Theme: Changes the background color depending on whether it’s day or night at the selected location. -## View it live +Responsive Design: The app is mobile-friendly and adapts to various screen sizes. + +# Technical Details +Weather Data: The app uses the OpenWeatherMap API to fetch current weather and forecast data. + +JavaScript: The app uses fetch for asynchronous HTTP requests and updates the DOM dynamically based on the fetched data. + +HTML & CSS: The layout is built using HTML and CSS. The app's design changes dynamically based on the time of day (i.e., day/night background). + +Event Listeners: Includes functionality for toggling a search window, submitting a city name, and handling API responses. -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. +# File Structure +index.html: The main HTML structure of the weather app, including input forms and layout for displaying weather information. + +style.css: The stylesheet for the app, responsible for styling the layout, background colors, icons, and responsiveness. + +script.js: The JavaScript file that handles all the functionality of the app, such as fetching data from the OpenWeatherMap API, updating the UI, and managing events (searching for cities, toggling the search window, etc.). + +# Improvements +Currently, the weather forecast is based on two time entry points (12 AM and 12 PM) to retrieve the temperature data. To enhance the accuracy of the displayed forecast, it would be beneficial to fetch data at more frequent intervals, such as every 3 or 6 hours, to provide a more detailed and precise temperature trend throughout the day. This would give users a clearer and more reliable forecast. + +## View it live +https://gittes-weather-app.netlify.app \ No newline at end of file diff --git a/assets/.DS_Store b/assets/.DS_Store new file mode 100644 index 000000000..7c762d848 Binary files /dev/null and b/assets/.DS_Store differ diff --git a/index.html b/index.html new file mode 100644 index 000000000..f58d18dc4 --- /dev/null +++ b/index.html @@ -0,0 +1,61 @@ + + + + + + + + + Weather App + + + + + + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 000000000..0fedc79e3 --- /dev/null +++ b/script.js @@ -0,0 +1,176 @@ +const BASE_URL = "https://api.openweathermap.org/data/2.5/" +const API_KEY = "72e635da2875c352d9f726550253e2db" + +const menu = document.getElementById('hamburgerMenu') +const searchWindow = document.getElementById('searchLocationWindow') +const closeWindow = document.getElementById('closeWindow') +const searchButton = document.getElementById('searchLocationButton') +const cityInput = document.getElementById('searchCity') +const temperature = document.getElementById('temp') +const weather = document.getElementById('weather') +const cityName = document.getElementById('cityName') +const sunrise = document.getElementById('sunrise') +const sunset = document.getElementById('sunset') +const iconCurrent = document.getElementById('currentWeatherImage') +const header = document.getElementById('header') + +// function to create new URL for forecast based on the users input +const createForecastURL = (cityName) => { + return `${BASE_URL}forecast?q=${cityName}&units=metric&APPID=${API_KEY}` +} + +// function to create new URL for current weather based on the users input +const createURL = (cityName) => { + return `${BASE_URL}weather?q=${cityName}&units=metric&APPID=${API_KEY}` +} + +// function to convert Unix-time into hours:minutes +const convertUnixToTime = (unixTime, timeZone) => { + const time = unixTime * 1000 + const date = new Date(time) + const localTime = new Date(date.getTime() + timeZone * 1000) + const hours = localTime.getUTCHours() + const minutes = "0" + localTime.getUTCMinutes() + return `${hours}:${minutes.substr(-2)}` +} + +// to get the weather data for the current weather +const fetchWeatherData = async (cityName) => { + const URL = createURL(cityName) + try { + const response = await fetch(URL) + if (!response.ok) { + throw new Error('Failed to fetch weather data') + } + const data = await response.json() + updateHTML(data) + } catch (error) { + console.log(error) + } +} + +// to get the forecast weather data +const fetchForecastData = async (cityName) => { + const URLForecast = createForecastURL(cityName) + try { + const response = await fetch(URLForecast) + if (!response.ok) { + throw new Error('Failed to fetch forecast weather data') + } + const dataForecast = await response.json() + console.log(dataForecast) + updateForecastHTML(dataForecast) + } catch (error) { + console.log(error) + } +} + +// update HTML current weather +const updateHTML = (data) => { + cityName.innerText = data.name + weather.innerText = data.weather[0].description + + // method to round the temperatures + const roundedTemp = Math.round(data.main.temp) + temperature.innerText = `${roundedTemp}°C` + + // the local time for the specific city + const sunriseTime = convertUnixToTime(data.sys.sunrise, data.timezone) + const sunsetTime = convertUnixToTime(data.sys.sunset, data.timezone) + + sunrise.innerText = sunriseTime + sunset.innerText = sunsetTime + + const currentTime = Math.floor(Date.now() / 1000) + + // Icon + const iconCode = data.weather[0].icon + const iconUrl = `http://openweathermap.org/img/wn/${iconCode}@2x.png` + + iconCurrent.innerHTML = `Weather Icon` + + // Day/Night Background Update + const nightBackground = (currentTime, sunriseTime, sunsetTime) => { + header.style.background = (currentTime < sunriseTime || currentTime > sunsetTime) + ? "linear-gradient(90deg, #323667 50%, #6B6EA8 50%)" + : "" + } + nightBackground(currentTime, data.sys.sunrise, data.sys.sunset) +} + +// update HTML for forecast weather +// Array with weekdays +const weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] + +// Function for HTML inputs +const updateForecastHTML = (dataForecast) => { + const forecasts = dataForecast.list + + // filters the forecast for 12 AM (midnight) each day + const filteredMinForecasts = forecasts.filter(minForecast => { + const dateTime = new Date(minForecast.dt_txt) + return dateTime.getHours() === 0 + }) + + // filters the forecast for 12AM each day + const filteredMaxForecasts = forecasts.filter(maxForecast => { + const dateTime = new Date(maxForecast.dt_txt) + return dateTime.getHours() === 12 + }) + let forecastHTML = '' + + // Loops + for (let i = 0; i < 4; i++) { + const minTemp = Math.round(filteredMinForecasts[i].main.temp) + const maxTemp = Math.round(filteredMaxForecasts[i].main.temp) + + // Get the icon for the forecast + const iconCode = filteredMinForecasts[i].weather[0].icon + const iconUrl = `http://openweathermap.org/img/wn/${iconCode}@2x.png` + + // get weekdays + const dateTime = new Date(filteredMaxForecasts[i].dt_txt) + const dayOfWeek = weekdays[dateTime.getDay()] + + forecastHTML += ` +
+

${dayOfWeek}

+
+ Weather Icon +
+

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

+
+ ` + } + forecastContainer.innerHTML = forecastHTML +} + +// Event Listener to open the search window +menu.addEventListener("click", () => { + searchWindow.style.display = "block" + cityInput.value = "" +}) + +// To close the search window users can either click on the X or somewhere else on the window +closeWindow.addEventListener("click", () => { + searchWindow.style.display = "none" +}) +searchWindow.addEventListener("click", (event) => { + if (event.target == searchWindow) { + searchWindow.style.display = "none" + } +}) + +// Event-Listener to be able to search a cities +searchButton.addEventListener("click", () => { + // gets the name of the city from the input field + const city = cityInput.value + console.log(city) + if (city) { + fetchWeatherData(city) + fetchForecastData(city) + searchWindow.style.display = "none" + } else ( + console.log("please enter a city name") + ) +}) \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 000000000..390282251 --- /dev/null +++ b/style.css @@ -0,0 +1,250 @@ +/* for iPhone XR */ +body { + padding: 0; + max-width: 100%; + margin: 0; + font-family: Roboto; +} + +header { + background: linear-gradient(to bottom right, #8589FF, #E8E9FF); + height: 60%; + width: 100%; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + border-bottom-left-radius: 50% 10%; + border-bottom-right-radius: 50% 10%; +} + +.menu { + width: 35px; + height: 30px; + display: flex; + flex-direction: column; + justify-content: space-around; + margin-left: 30px; + margin-top: 20px; + cursor: pointer; +} + +.hamburger-layer { + width: 100%; + border: 3px; + height: 4px; + border-top: solid #FFFFFF; +} + +/* Search town window */ +.search-location-window { + z-index: 1; + width: 200px; + height: 100px; + position: fixed; + left: 0; + top: 0; + display: flex; + flex-direction: column; + justify-items: center; + align-items: center; +} + +.search-city { + display: flex; + height: 30px; + width: 100px; + max-width: 300px; + border-radius: 10px; + border: none; + margin: 10px 0px 10px 0px; +} + +.search-button { + display: flex; + justify-content: center; + align-items: center; + height: 30px; + border-radius: 10px; + border: none; +} + +.searchLocationContainer { + position: relative; + padding: 20px; + color: #FFFFFF; + background-color: #8589FF; +} + +@media(max-width:400px) { + .searchLocationContainer { + padding: 5px; + margin: 10%; + } +} + +.weather-container { + display: flex; + flex-direction: row; +} + +.temperature-container { + max-width: 200px; + max-height: 200px; + margin-top: 40px; + margin-left: 30px; + text-align: left; + color: #FFFFFF; + display: flex; + flex-direction: column; + position: absolute; +} + +.temperature { + display: flex; + flex-direction: row; +} + +h1 { + font-size: 100px; + font-weight: 300; + margin: 0; + line-height: 140px; +} + +.celsius { + font-size: 40px; + line-height: 106px; +} + +h2 { + font-size: 34px; + font-weight: 300; + margin: 0; + line-height: 40px; +} + +h3 { + font-size: 22px; + font-weight: 300; + margin: 0; + line-height: 25px; +} + +.current-weather-image { + width: 160px; + height: 152px; + margin-top: 5px; + margin-left: 225px; + position: relative; +} + +.icon-big { + width: 100%; +} + +.sunrise-sunset { + display: flex; + width: 80%; + max-width: 400px; + margin-top: 100px; + margin-left: 30px; + justify-content: space-between; +} + +p { + font-weight: 300; + font-size: 22px; + line-height: 25px; + color: #FFFFFF; +} + +main { + height: 40%; + display: flex; + justify-content: center; +} + +.weather-forecast-container { + min-width: 320px; + max-width: 800px; + min-height: 250px; + margin-top: 20px; + display: flex; + flex-direction: column; + justify-content: center; +} + +.weather-day { + width: 100%; + height: 50px; + display: flex; + flex-direction: row; + justify-content: space-around; + align-items: center; + border-radius: 30px; +} + +.weather-day:hover { + background-color: #d1cdcd; +} + +.forecast-image { + width: 50px; +} + +.forecast-icon { + width: 100%; +} + +.weekday, +.forecast-temperature { + color: #707070; + font-size: 20px; +} + +/* Tablets and iPads */ +@media(min-width:667px) { + .weather-container { + justify-content: center; + } + + header { + justify-content: center; + } + + .sunrise-sunset { + max-width: 100%; + justify-content: space-around; + } + + .current-weather-image { + width: 200px; + height: 200px; + margin-left: 400px; + } +} + +/* desktop */ +@media(min-width: 1024px) { + body { + display: flex; + flex-direction: row; + background: linear-gradient(to bottom right, #8589FF, #E8E9FF); + } + + header { + max-width: 50%; + height: 80%; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + + .weather-forecast-container { + margin-top: 150px; + margin-left: 100px; + display: flex; + flex-direction: column; + justify-content: center; + } +} \ No newline at end of file