Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Joyce Kuo Weather App #423

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
# Weather App

Replace this readme with your own information about your project.
This project retrieves real-time weather data from a weather API, showcasing the current temperature and a 5-day forecast for a default city. Additional features allow users to cycle through various cities with a button click and use a search bar to find weather information for any specified city.

Start by briefly describing the assignment in a sentence or two. Keep it short and to the point.

## 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?
I began by following the instructions step by step, finding it helpful to break tasks into small and manageable parts. Unexpected challenges came up along the way. For instance, displaying the high and low temperatures in a 5-day forecast required multiple steps. Initially, the high and low temperatures I displayed were identical because I was only retrieving data from a 3-hour span, which didn't show much variation. To address this, I made sure to collect data over a 24-hour period and then identify the highest highs and lowest lows during that time. Throughout the process, I took notes with pen and paper, adding smaller tasks under larger goals as they emerged.

I used ChatGPT for help with syntax questions and design challenges, such as creating the bottom curve for the main weather info.

Given more time, I would refine my code to eliminate any unnecessary elements left over from my problem-solving experiments. Additionally, I would explore CSS animations to create more engaging visuals. I would also incorporate more weather data and integrate a geolocation API for enhanced functionality.


## 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://joycekuoweatherapp.netlify.app/
Binary file removed assets/design-1/Group16.png
Binary file not shown.
Binary file removed assets/design-1/Group34.png
Binary file not shown.
Binary file removed assets/design-1/Group36.png
Binary file not shown.
Binary file removed assets/design-1/Group37.png
Binary file not shown.
Binary file removed assets/design-1/Group38.png
Binary file not shown.
Binary file added assets/design-1/Group40.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/design-1/day-clear.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/design-1/day-clouds.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/design-1/day-fog.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/design-1/day-rain.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/design-1/day-snow.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/design-1/night-clear.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/design-1/night-clouds.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/design-1/night-fog.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/design-1/night-rain.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/design-1/night-snow.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
67 changes: 67 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="styles.css">
<link
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<title>Joyce's Weather App</title>
</head>

<body>

<div class="layout">

<div id="weather-overlay" class="overlay-container"></div>

<div class="top-container" id="background-container">
<div class="search-container">
<i id="toggle-icon" class="fas fa-search" onclick="toggleSearch()"></i>
<div id="search-box" class="hidden">
<input type="text" id="search-input" placeholder="Enter city name" />
<i class="fas fa-search" id="input-search-icon"></i>
</div>
</div>
<div class="main-info">
<div class="temperature">
<span class="temp-number" id="current-temperature"></span><span class="temp-unit">°C</span>
</div>
<p class="city" id="city-name">A city</p>
<div class="weather">
<p class="description" id="current-weather">Clear</p>
<img class="icon" id="main-icon" alt="Weather icon">
</div>
</div>
<div class="sunrise-sunset" id="sunrise-sunset-times">
<span>sunrise</span>
<span id="sunrise-time"></span>
<span>sunset</span>
<span id="sunset-time"></span>
</div>
</div>

<div>
<img src="./assets/design-1/Group40.png" alt="Clickable icon to load next city" class="arrow-icon" id="arrow">
</div>

<div class="forecast-container" id="forecast">
<div class="forecast-row">
<div class="forecast-day"></div>
<div class="forecast-icon"></div>
<div class="forecast-temp"></div>
</div>
</div>

</div>

<footer>Created by: Joyce Kuo</footer>

</body>

<script src="script.js"></script>

</html>
2 changes: 1 addition & 1 deletion instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Start out with signing up for a [free Open Weather Map](https://home.openweather
For example, to get the current weather in Stockholm, you can use the URL below. Remember to replace YOUR_API_KEY with the API key you copied from your dashboard.

```
https://api.openweathermap.org/data/2.5/weather?q=Stockholm,Sweden&units=metric&APPID=YOUR_API_KEY
https://api.openweathermap.org/data/2.5/weather?q=Stockholm,Sweden&units=metric&APPID=248332e11aac477643699fc267736540
```

The response should look something like this (this has been run through jsonlint.com to add newlines and indentation):
Expand Down
5 changes: 3 additions & 2 deletions pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
## Netlify link
Add your Netlify link here.
PS. Don't forget to add it in your readme as well.

https://joycekuoweatherapp.netlify.app/

248 changes: 248 additions & 0 deletions script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
const cities = ['Stenungsund', 'New York', 'Bermuda', 'Hong Kong', 'London']
let currentIndex = 0

const API_KEY = "248332e11aac477643699fc267736540"
const BASE_URL = "https://api.openweathermap.org/data/2.5/weather?"
const FORECAST_BASE_URL = "https://api.openweathermap.org/data/2.5/forecast?"
const city = "San Jose"
const URL = `${BASE_URL}q=${city}&units=metric&APPID=${API_KEY}`
const FORECAST_URL = `${FORECAST_BASE_URL}q=${city}&units=metric&APPID=${API_KEY}`

//DOM selectors
const cityName = document.getElementById("city-name")
const weather = document.getElementById("current-weather")
const temperature = document.getElementById("current-temperature")
const sunriseElement = document.getElementById("sunrise-time")
const sunsetElement = document.getElementById("sunset-time")
const weatherIcon = document.getElementById("main-icon")
const forecastContainer = document.getElementById('forecast')
const searchInput = document.getElementById('search-input')
const searchIcon = document.getElementById('input-search-icon')
const arrowIcon = document.getElementById('arrow')
const containerBackground = document.getElementById('background-container')
const overlayContainer = document.getElementById('weather-overlay')

// Function to capitalize first letter of each word
const capitalizeFirstLetter = (str) => {
return str.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')
}

// Function to format timestamp properly
const formatTime = (timestamp, timezoneOffset) => {
const date = new Date((timestamp + timezoneOffset) * 1000)
const hours = date.getUTCHours().toString().padStart(2, '0')
const minutes = date.getUTCMinutes().toString().padStart(2, '0')
return `${hours}:${minutes}`
}

// Function to change overlay color based on temp
const setOverlayColorAnimation = (currentTemp) => {
if (currentTemp >= 20) {
overlayContainer.classList.add('warm')
overlayContainer.classList.remove('cold')
} else {
overlayContainer.classList.add('cold')
overlayContainer.classList.remove('warm')
}
}

// Display today's weather
const updateHTML = (data) => {

const currentCity = data.name
const currentWeather = capitalizeFirstLetter(data.weather[0].description)
const currentTemp = Math.round(data.main.temp)
const timezoneOffset = data.timezone
const sunriseTimestamp = formatTime(data.sys.sunrise, timezoneOffset)
const sunsetTimestamp = formatTime(data.sys.sunset, timezoneOffset)
const currentIcon = data.weather[0].icon

cityName.innerText = currentCity
weather.innerText = currentWeather
temperature.innerHTML = `${currentTemp}`
sunriseElement.innerText = sunriseTimestamp
sunsetElement.innerText = sunsetTimestamp
weatherIcon.src = `http://openweathermap.org/img/wn/${currentIcon}@2x.png`

setOverlayColorAnimation(currentTemp)

}

// Display 5 day forecast
const updateForecastHTML = (data) => {
forecastContainer.innerHTML = '' // Clear previous forecast
const forecastArray = data.list

// Group forecasts by day
const dailyForecasts = {}
forecastArray.forEach(entry => {
const date = new Date(entry.dt * 1000)
const day = date.toLocaleDateString('en-SE', { weekday: 'short' })
const today = new Date().toLocaleDateString('en-SE', { weekday: 'short' })
if (day === today) return // Skip today's forecast
if (!dailyForecasts[day]) {
dailyForecasts[day] = []
}
dailyForecasts[day].push(entry)
})

// Process each day's forecast
Object.keys(dailyForecasts).forEach(day => {
const forecasts = dailyForecasts[day]
let tempHigh = -Infinity
let tempLow = Infinity
let icon = forecasts[0].weather[0].icon

forecasts.forEach(forecast => {
if (forecast.main.temp_max > tempHigh) {
tempHigh = forecast.main.temp_max
}
if (forecast.main.temp_min < tempLow) {
tempLow = forecast.main.temp_min
}
})

const forecastRow = document.createElement('div')
forecastRow.classList.add('forecast-row')
forecastRow.innerHTML = `
<div class="forecast-day">${day}</div>
<div class="forecast-icon">
<img src="http://openweathermap.org/img/wn/${icon}@2x.png" alt=${forecasts[0].weather[0].description}>
</div>
<div class="forecast-temp">${Math.round(tempHigh)} / ${Math.round(tempLow)}°C</div>
`
forecastContainer.appendChild(forecastRow)
})
}

// Toggle search icon to reveal search bar
const toggleSearch = () => {
const toggleIcon = document.getElementById('toggle-icon')
const searchBox = document.getElementById('search-box')

// Toggle search box visiblity
if (searchBox.classList.contains('hidden')) {
searchBox.classList.remove('hidden')
searchBox.classList.add('show')
} else {
searchBox.classList.remove('show')
searchBox.classList.add('hidden')
}
// Switch between search and close icon
if (toggleIcon.classList.contains('fa-search')) {
toggleIcon.classList.remove('fa-search')
toggleIcon.classList.add('fa-times')
} else {
toggleIcon.classList.remove('fa-times')
toggleIcon.classList.add('fa-search')
}
}

// Fetch and display weather data for a searched city
const fetchWeatherData = (city) => {
const SEARCH_URL = `${BASE_URL}q=${city}&units=metric&APPID=${API_KEY}`
const SEARCH_FORECAST_URL = `${FORECAST_BASE_URL}q=${city}&units=metric&APPID=${API_KEY}`

fetch(SEARCH_URL)
.then(response => response.json())
.then(data => {
const weatherId = data.weather[0].id
const timezoneOffset = data.timezone // timezone offset in seconds
setBackgroundBasedOnWeatherId(weatherId, timezoneOffset)
updateHTML(data)
})
.catch(error => console.error('Error:', error))

fetch(SEARCH_FORECAST_URL)
.then(response => response.json())
.then(data => {
updateForecastHTML(data)
})
.catch(error => console.error('Error:', error))
}

// Event listener for search input
const addSearchEventListener = (element, eventType) => {
element.addEventListener(eventType, () => {
const searchCity = searchInput.value;
fetchWeatherData(searchCity);
searchInput.value = ''
});
};

addSearchEventListener(searchIcon, 'click');

searchInput.addEventListener('keypress', (event) => {
if (event.key === 'Enter') {
fetchWeatherData(searchInput.value);
searchInput.value = ''
}
})

// Cycle through city array
const cycleCities = () => {
fetchWeatherData(cities[currentIndex])
currentIndex = (currentIndex + 1) % cities.length
}

arrowIcon.addEventListener('click', cycleCities)

fetch(URL)
.then(response => response.json())
.then(data => {
const weatherId = data.weather[0].id
const timezoneOffset = data.timezone
setBackgroundBasedOnWeatherId(weatherId, timezoneOffset)
updateHTML(data) //Call updateHTML with the fetched data
})
.catch(error => console.error('Error:', error))

fetch(FORECAST_URL)
.then(response => response.json())
.then(data => {
updateForecastHTML(data)
})
.catch(error => console.error('Error:', error))

// Get time of day in local time
const getTimeOfDay = (timezoneOffset) => {
const localTime = new Date().getTime() + timezoneOffset * 1000
const cityTime = new Date(localTime)
const currentHour = cityTime.getUTCHours() // Get the hour in the city's local time
return (currentHour > 6 && currentHour < 18 ? 'day' : 'night')
}

// Function to change background image based on weather and time
const setBackgroundBasedOnWeatherId = (weatherId, timezoneOffset) => {
const timeOfDay = getTimeOfDay(timezoneOffset)
const firstDigit = weatherId.toString()[0]

let backgroundImage = ""

switch (true) {

case weatherId === 800: // Clear Sky (800)
backgroundImage = timeOfDay === 'day' ? "url('./assets/design-1/day-clear.jpg')" : "url('./assets/design-1/night-clear.jpg')"
break
case firstDigit === '2': // Thunderstorm (2xx)
case firstDigit === '3': // Drizzle (3xx)
case firstDigit === '5': // Rain (5xx)
backgroundImage = timeOfDay === 'day' ? "url('./assets/design-1/day-rain.jpg')" : "url('./assets/design-1/night-rain.jpg')"
break
case firstDigit === '6': // Snow (6xx)
backgroundImage = timeOfDay === 'day' ? "url('./assets/design-1/day-snow.jpg')" : "url('./assets/design-1/night-snow.jpg')"
break
case firstDigit === '7': // Atmosphere (Mist, Smoke, Fog)
backgroundImage = timeOfDay === 'day' ? "url('./assets/design-1/day-fog.jpg')" : "url('./assets/design-1/night-fog.jpg')"
break
case firstDigit === '8': // Clouds (80x)
backgroundImage = timeOfDay === 'day' ? "url('./assets/design-1/day-clouds.jpg')" : "url('./assets/design-1/night-clouds.jpg')"
break

default:
backgroundImage = ""
break
}

containerBackground.style.backgroundImage = backgroundImage
}
Loading