From 3b047cf58d117436f3954bca8eaa9e3b706e4dc1 Mon Sep 17 00:00:00 2001 From: meccacodes Date: Fri, 6 Dec 2024 12:59:12 -0800 Subject: [PATCH 1/6] App requirements up and running, needs styling --- README.md | 10 ++++-- index.html | 43 +++++++++++++++++++++++++ main.js | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ styles.css | 26 +++++++++++++++ 4 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 index.html create mode 100644 main.js create mode 100644 styles.css diff --git a/README.md b/README.md index 4638c655..4df26944 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ -## Weather Project +# Eval 3 -This project has been created by a student at Parsity, an online software engineering course. The work in this repository is wholly of the student based on a sample starter project that can be accessed by looking at the repository that this project forks. +## Weather App -If you have any questions about this project or the program in general, visit [parsity.io](https://parsity.io/) or email hello@parsity.io. +### Mecca Conway + +#### Cohort 14 + +##### Part Time diff --git a/index.html b/index.html new file mode 100644 index 00000000..a16b242f --- /dev/null +++ b/index.html @@ -0,0 +1,43 @@ + + + + + + Weather App + + + + +
+

Weather App

+
+ + +
+
+
+
+

City Name

+ +

0°F

+

Description

+
+
+
+

5-Day Forecast

+
+
+ + + + diff --git a/main.js b/main.js new file mode 100644 index 00000000..5b3cc8b7 --- /dev/null +++ b/main.js @@ -0,0 +1,94 @@ +const apiKey = "3f88d5f8bbfa8eb651ea5f78d37f3578"; +const currentWeatherDiv = document.getElementById("currentWeather"); +const forecastDiv = document.getElementById("forecast"); +const searchBtn = document.getElementById("searchBtn"); +const locationInput = document.getElementById("locationInput"); + +// Function to fetch weather data +async function fetchWeatherData(lat, lon) { + try { + const response = await fetch( + `https://api.openweathermap.org/data/3.0/onecall?lat=${lat}&lon=${lon}&exclude=minutely,hourly&appid=${apiKey}&units=imperial` + ); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + return data; + } catch (error) { + console.error("Error fetching weather data:", error); + } +} + +// Function to get approximate location based on IP address +async function getApproximateLocation() { + const response = await fetch("http://ip-api.com/json"); + const data = await response.json(); + return { lat: data.lat, lon: data.lon }; +} + +// Function to display current weather +function displayCurrentWeather(data) { + const { temp, weather } = data.current; + + document.getElementById("cityName").innerText = data.timezone; // Set city name + document.getElementById("temperature").innerText = `${temp.toFixed(1)}°F`; // Set temperature in Fahrenheit + document.getElementById("weatherDescription").innerText = + weather[0].description; // Set description + document.getElementById( + "weatherIcon" + ).src = `http://openweathermap.org/img/wn/${weather[0].icon}.png`; // Set icon +} + +// Function to display 5-day forecast +function displayForecast(daily) { + forecastDiv.innerHTML = ""; + daily.slice(1, 6).forEach((day) => { + const date = new Date(day.dt * 1000).toLocaleDateString(); + const { temp, weather } = day; + forecastDiv.innerHTML += ` +
+
+
+
${date}
+

${temp.day.toFixed( + 1 + )}°F

+

${weather[0].description}

+ ${weather[0].description} +
+
+
`; + }); +} + +// Event listener for search button +searchBtn.addEventListener("click", async () => { + const location = locationInput.value; + const geoResponse = await fetch( + `https://api.openweathermap.org/geo/1.0/direct?q=${location}&appid=${apiKey}` + ); + const geoData = await geoResponse.json(); + + if (geoData.length > 0) { + const { lat, lon } = geoData[0]; + const weatherData = await fetchWeatherData(lat, lon); + if (weatherData) { + displayCurrentWeather(weatherData); + displayForecast(weatherData.daily); + } + } else { + alert("Location not found."); + } +}); + +// Initialize with approximate location from IP +getApproximateLocation().then(async (location) => { + const weatherData = await fetchWeatherData(location.lat, location.lon); + if (weatherData) { + displayCurrentWeather(weatherData); + displayForecast(weatherData.daily); + } +}); diff --git a/styles.css b/styles.css new file mode 100644 index 00000000..1154131c --- /dev/null +++ b/styles.css @@ -0,0 +1,26 @@ +body { + background: #253248; + color: white; +} + +.card { + border-radius: 15px; +} + +.card-body img { + width: 50px; +} + +/* Centering styles */ +#currentWeather { + display: flex; +} + +/* Responsive adjustments for forecast */ +.forecast-card { + margin-bottom: 15px; /* Add some spacing between cards */ +} + +#temperature { + margin-left: 10px; +} From 6f649c70184a1672461c2303cd53cbf82568e14d Mon Sep 17 00:00:00 2001 From: meccacodes Date: Fri, 6 Dec 2024 13:24:19 -0800 Subject: [PATCH 2/6] Styling improvements, removed comments to clean up code --- index.html | 10 +++++----- main.js | 26 ++++++++++---------------- styles.css | 42 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 54 insertions(+), 24 deletions(-) diff --git a/index.html b/index.html index a16b242f..6e2a64f1 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - Weather App + Weatherific -

Weather App

+

Weather-ific!

Weather App class="form-control" placeholder="Enter city or zip code" /> - +
-
+

City Name

0°F

@@ -35,7 +35,7 @@

0°F

5-Day Forecast

-
+
diff --git a/main.js b/main.js index 5b3cc8b7..e1e96892 100644 --- a/main.js +++ b/main.js @@ -4,7 +4,6 @@ const forecastDiv = document.getElementById("forecast"); const searchBtn = document.getElementById("searchBtn"); const locationInput = document.getElementById("locationInput"); -// Function to fetch weather data async function fetchWeatherData(lat, lon) { try { const response = await fetch( @@ -20,40 +19,37 @@ async function fetchWeatherData(lat, lon) { } } -// Function to get approximate location based on IP address async function getApproximateLocation() { const response = await fetch("http://ip-api.com/json"); const data = await response.json(); return { lat: data.lat, lon: data.lon }; } -// Function to display current weather function displayCurrentWeather(data) { const { temp, weather } = data.current; - document.getElementById("cityName").innerText = data.timezone; // Set city name - document.getElementById("temperature").innerText = `${temp.toFixed(1)}°F`; // Set temperature in Fahrenheit + document.getElementById("cityName").innerText = data.timezone; + document.getElementById("temperature").innerText = `${temp.toFixed(1)}°F`; document.getElementById("weatherDescription").innerText = - weather[0].description; // Set description + weather[0].description; document.getElementById( "weatherIcon" - ).src = `http://openweathermap.org/img/wn/${weather[0].icon}.png`; // Set icon + ).src = `http://openweathermap.org/img/wn/${weather[0].icon}.png`; } -// Function to display 5-day forecast function displayForecast(daily) { forecastDiv.innerHTML = ""; daily.slice(1, 6).forEach((day) => { - const date = new Date(day.dt * 1000).toLocaleDateString(); + const date = new Date(day.dt * 1000); + const options = { weekday: "long" }; + const dayName = date.toLocaleDateString("en-US", options); const { temp, weather } = day; forecastDiv.innerHTML += ` -
+
-
${date}
-

${temp.day.toFixed( - 1 - )}°F

+

${dayName}

+

${temp.day.toFixed(1)}°F

${weather[0].description}

{ const location = locationInput.value; const geoResponse = await fetch( @@ -84,7 +79,6 @@ searchBtn.addEventListener("click", async () => { } }); -// Initialize with approximate location from IP getApproximateLocation().then(async (location) => { const weatherData = await fetchWeatherData(location.lat, location.lon); if (weatherData) { diff --git a/styles.css b/styles.css index 1154131c..7f7e5dcd 100644 --- a/styles.css +++ b/styles.css @@ -1,6 +1,25 @@ body { background: #253248; color: white; + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + margin: 0; +} + +h1 { + text-align: center; +} + +.input-group { + display: flex; + justify-content: center; + align-items: center; +} + +#searchBtn { + margin-left: 10px; } .card { @@ -11,16 +30,33 @@ body { width: 50px; } -/* Centering styles */ #currentWeather { display: flex; + justify-content: center; + align-items: center; + margin-top: 50px; + margin-bottom: 50px; +} + +#cityWeather { + border: 1px white solid; + padding: 15px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; } -/* Responsive adjustments for forecast */ .forecast-card { - margin-bottom: 15px; /* Add some spacing between cards */ + margin: 15px; } #temperature { margin-left: 10px; } + +#forecast { + display: flex; + flex-direction: row; + width: 100%; +} From fcfd58597be098b743160540112eecb0cc1df496 Mon Sep 17 00:00:00 2001 From: meccacodes Date: Fri, 6 Dec 2024 13:27:18 -0800 Subject: [PATCH 3/6] More styling changes --- styles.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/styles.css b/styles.css index 7f7e5dcd..4b43fb56 100644 --- a/styles.css +++ b/styles.css @@ -18,8 +18,15 @@ h1 { align-items: center; } +#locationInput { + font-size: 20px; + padding: 5px; +} + #searchBtn { margin-left: 10px; + font-size: 20px; + padding: 5px; } .card { From 9d427ae67de65eeba182365a0fe72b78208c3336 Mon Sep 17 00:00:00 2001 From: meccacodes Date: Sat, 7 Dec 2024 07:47:36 -0800 Subject: [PATCH 4/6] More Styling --- styles.css | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/styles.css b/styles.css index 4b43fb56..5b4f639b 100644 --- a/styles.css +++ b/styles.css @@ -54,16 +54,47 @@ h1 { align-items: center; } -.forecast-card { - margin: 15px; -} - #temperature { margin-left: 10px; } #forecast { display: flex; - flex-direction: row; - width: 100%; + justify-content: space-evenly; + align-items: stretch; + flex-wrap: nowrap; + margin-top: 15px; +} + +.forecast-card { + border: 1px solid white; + padding: 10px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + flex-basis: calc(20% - 30px); + margin-right: 15px; + min-width: 150px; +} + +.card-body { + display: flex; + flex-direction: column; + justify-content: center; + align-item: center; + align-items: center; +} + +@media (max-width: 768px) { + #forecast { + flex-direction: column; + align-items: center; + } + + .forecast-card { + width: 100%; + margin-right: 0; + margin-bottom: 15px; + } } From 7f595fbde4e8d15d734f2f64f6e39d6951a4695b Mon Sep 17 00:00:00 2001 From: meccacodes Date: Sat, 7 Dec 2024 07:56:53 -0800 Subject: [PATCH 5/6] Fixed zip code bug --- main.js | 79 +++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 13 deletions(-) diff --git a/main.js b/main.js index e1e96892..e3bb8c46 100644 --- a/main.js +++ b/main.js @@ -22,13 +22,13 @@ async function fetchWeatherData(lat, lon) { async function getApproximateLocation() { const response = await fetch("http://ip-api.com/json"); const data = await response.json(); - return { lat: data.lat, lon: data.lon }; + return { lat: data.lat, lon: data.lon, name: data.city }; } -function displayCurrentWeather(data) { +function displayCurrentWeather(data, cityName) { const { temp, weather } = data.current; - document.getElementById("cityName").innerText = data.timezone; + document.getElementById("cityName").innerText = cityName; document.getElementById("temperature").innerText = `${temp.toFixed(1)}°F`; document.getElementById("weatherDescription").innerText = weather[0].description; @@ -60,29 +60,82 @@ function displayForecast(daily) { }); } +function isValidZipCode(zip) { + if (zip.length === 5) { + return zip.split("").every((char) => char >= "0" && char <= "9"); + } else if (zip.length === 10) { + return ( + zip[5] === "-" && + zip + .slice(0, 5) + .split("") + .every((char) => char >= "0" && char <= "9") && + zip + .slice(6) + .split("") + .every((char) => char >= "0" && char <= "9") + ); + } + return false; +} + +function isValidZipCode(zip) { + if (zip.length === 5) { + return zip.split("").every((char) => char >= "0" && char <= "9"); + } else if (zip.length === 10) { + return ( + zip[5] === "-" && + zip + .slice(0, 5) + .split("") + .every((char) => char >= "0" && char <= "9") && + zip + .slice(6) + .split("") + .every((char) => char >= "0" && char <= "9") + ); + } + return false; +} + searchBtn.addEventListener("click", async () => { const location = locationInput.value; - const geoResponse = await fetch( - `https://api.openweathermap.org/geo/1.0/direct?q=${location}&appid=${apiKey}` - ); - const geoData = await geoResponse.json(); + let geoUrl; + + if (isValidZipCode(location)) { + geoUrl = `https://api.openweathermap.org/geo/1.0/zip?zip=${location}&appid=${apiKey}`; + } else { + geoUrl = `https://api.openweathermap.org/geo/1.0/direct?q=${location}&limit=1&appid=${apiKey}`; + } + + try { + const geoResponse = await fetch(geoUrl); + const geoData = await geoResponse.json(); + + let lat, lon, name; + if (Array.isArray(geoData) && geoData.length > 0) { + ({ lat, lon, name } = geoData[0]); + } else if (geoData.lat && geoData.lon) { + ({ lat, lon, name } = geoData); + } else { + throw new Error("Location not found"); + } - if (geoData.length > 0) { - const { lat, lon } = geoData[0]; const weatherData = await fetchWeatherData(lat, lon); if (weatherData) { - displayCurrentWeather(weatherData); + displayCurrentWeather(weatherData, name); displayForecast(weatherData.daily); } - } else { - alert("Location not found."); + } catch (error) { + console.error("Error:", error); + alert(error.message); } }); getApproximateLocation().then(async (location) => { const weatherData = await fetchWeatherData(location.lat, location.lon); if (weatherData) { - displayCurrentWeather(weatherData); + displayCurrentWeather(weatherData, location.name); displayForecast(weatherData.daily); } }); From 253a11f68fdd242b71b73f1210ab23d884f2a9e3 Mon Sep 17 00:00:00 2001 From: meccacodes Date: Sat, 7 Dec 2024 08:17:16 -0800 Subject: [PATCH 6/6] Add functionality to search by pressing enter, refractored code to be less DRY --- main.js | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/main.js b/main.js index e3bb8c46..d43c35c1 100644 --- a/main.js +++ b/main.js @@ -22,7 +22,13 @@ async function fetchWeatherData(lat, lon) { async function getApproximateLocation() { const response = await fetch("http://ip-api.com/json"); const data = await response.json(); - return { lat: data.lat, lon: data.lon, name: data.city }; + return { + lat: data.lat, + lon: data.lon, + name: `${data.city}, ${data.regionName}, ${data.country}`, + zip: data.zip, + timezone: data.timezone, + }; } function displayCurrentWeather(data, cityName) { @@ -79,29 +85,11 @@ function isValidZipCode(zip) { return false; } -function isValidZipCode(zip) { - if (zip.length === 5) { - return zip.split("").every((char) => char >= "0" && char <= "9"); - } else if (zip.length === 10) { - return ( - zip[5] === "-" && - zip - .slice(0, 5) - .split("") - .every((char) => char >= "0" && char <= "9") && - zip - .slice(6) - .split("") - .every((char) => char >= "0" && char <= "9") - ); - } - return false; -} +async function handleWeatherSearch() { + const location = locationInput.value.trim(); + if (!location) return; -searchBtn.addEventListener("click", async () => { - const location = locationInput.value; let geoUrl; - if (isValidZipCode(location)) { geoUrl = `https://api.openweathermap.org/geo/1.0/zip?zip=${location}&appid=${apiKey}`; } else { @@ -130,6 +118,15 @@ searchBtn.addEventListener("click", async () => { console.error("Error:", error); alert(error.message); } +} + +searchBtn.addEventListener("click", handleWeatherSearch); + +locationInput.addEventListener("keydown", (event) => { + if (event.key === "Enter") { + event.preventDefault(); + handleWeatherSearch(); + } }); getApproximateLocation().then(async (location) => {