Weather Widget

Rendered Component

Loading component...

Packages

npm install @fortawesome/react-fontawesome
npm install @fortawesome/free-solid-svg-icons
npm install framer-motion
yarn add @fortawesome/react-fontawesome
yarn add @fortawesome/free-solid-svg-icons
yarn add framer-motion

Code

Details

The Weather Widget is a compact component that presents current weather conditions and forecasts. It\'s designed to be easily integrated into dashboards or as a standalone feature on websites and apps.

Usage

Import the WeatherWidget component and provide it with the necessary weather data, either through props or by connecting it to a weather API.

Examples

  • Homepage weather display
  • Travel app destination weather
  • Smart home dashboard weather widget

Code


      'use client';
      import React, { useState, useEffect } from 'react';
      import { motion } from 'framer-motion';
      import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
      import {
        faCloudSun,
        faTemperatureHigh,
        faWind,
        faTint,
        faMapMarkerAlt,
      } from '@fortawesome/free-solid-svg-icons';

      const cities = [
        { name: 'New York', country: 'US' },
        { name: 'London', country: 'GB' },
        { name: 'Tokyo', country: 'JP' },
        { name: 'Paris', country: 'FR' },
        { name: 'Sydney', country: 'AU' },
        // Add more cities as needed
      ];

      const DEFAULT_CITY = cities[0]; // New York is the first city in the array

      const WeatherWidget = () => {
        const [weatherData, setWeatherData] = useState(null);
        const [loading, setLoading] = useState(true);
        const [error, setError] = useState(null);
        const [selectedCity, setSelectedCity] = useState(DEFAULT_CITY);
        const [usingGeolocation, setUsingGeolocation] = useState(false);
        const baseUrl = 'https://api.openweathermap.org/data/2.5/weather';
        const fetchWeather = async (params) => {
          const API_KEY = process.env.NEXT_PUBLIC_OPENWEATHERMAP_API_KEY;
          const baseUrl = 'https://api.openweathermap.org/data/2.5/weather';
          const queryParams = new URLSearchParams({
            ...params,
            appid: API_KEY,
            units: 'imperial', // Changed to imperial for Fahrenheit
          });

          try {
            const response = await fetch(`${baseUrl}?${queryParams}`);
            if (!response.ok) {
              throw new Error('Failed to fetch weather data');
            }
            const data = await response.json();
            setWeatherData(data);
            setLoading(false);
          } catch (error) {
            console.error('Error fetching weather data:', error);
            setError(error.message);
            setLoading(false);
          }
        };

        useEffect(() => {
          if (usingGeolocation) {
            navigator.geolocation.getCurrentPosition(
              (position) => {
                fetchWeather({
                  lat: position.coords.latitude,
                  lon: position.coords.longitude,
                });
              },
              () => {
                setUsingGeolocation(false);
                fetchWeather({ q: `${DEFAULT_CITY.name},${DEFAULT_CITY.country}` });
              }
            );
          } else {
            fetchWeather({ q: `${selectedCity.name},${selectedCity.country}` });
          }
        }, [selectedCity, usingGeolocation]);

        const handleCityChange = (event) => {
          const cityName = event.target.value;
          if (cityName === 'current') {
            setUsingGeolocation(true);
          } else {
            setUsingGeolocation(false);
            const city = cities.find((c) => c.name === cityName);
            setSelectedCity(city);
          }
        };

        if (loading)
          return <div className='text-zinc-200'>Loading weather data...</div>;
        if (error) return <div className='text-red-500'>Error: {error}</div>;

        return (
          <motion.div
            initial={{ opacity: 0, y: 20 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ duration: 0.5 }}
            className='bg-zinc-900 p-6 rounded-xl shadow-lg text-zinc-200'
          >
            <div className='flex justify-between items-center mb-4'>
              <h2 className='text-2xl font-bold flex items-center'>
                <FontAwesomeIcon icon={faCloudSun} className='mr-2' />
                Weather
              </h2>
              <select
                onChange={handleCityChange}
                className='bg-zinc-800 text-zinc-200 rounded-md p-2'
                value={usingGeolocation ? 'current' : selectedCity.name}
              >
                <option value='current'>Use My Location</option>
                {cities.map((city) => (
                  <option key={`${city.name}-${city.country}`} value={city.name}>
                    {city.name}, {city.country}
                  </option>
                ))}
              </select>
            </div>
            <div className='grid grid-cols-2 gap-4'>
              <div>
                <p className='text-4xl font-bold'>
                  {Math.round(weatherData.main.temp)}°F
                </p>
                <p className='text-gray-400 capitalize'>
                  {weatherData.weather[0].description}
                </p>
              </div>
              <div className='space-y-2'>
                <p className='flex items-center'>
                  <FontAwesomeIcon icon={faTemperatureHigh} className='mr-2 w-5 h-5' />
                  Feels like: {Math.round(weatherData.main.feels_like)}°F
                </p>
                <p className='flex items-center'>
                  <FontAwesomeIcon icon={faWind} className='mr-2 w-5 h-5' />
                  Wind: {Math.round(weatherData.wind.speed)} mph
                </p>
                <p className='flex items-center'>
                  <FontAwesomeIcon icon={faTint} className='mr-2 w-5 h-5' />
                  Humidity: {weatherData.main.humidity}%
                </p>
                <p className='flex items-center'>
                  <FontAwesomeIcon icon={faMapMarkerAlt} className='mr-2 w-5 h-5' />
                  {weatherData.name}, {weatherData.sys.country}
                </p>
              </div>
            </div>
          </motion.div>
        );
      };

      export default WeatherWidget;