import React, { useState, useEffect } from 'react';
import { Cloud, Sun, CloudRain, Snowflake, Wind, Droplets, Heart, BookOpen, Save, Star, Zap, Calendar } from 'lucide-react';
const WeatherPet = () => {
const [location, setLocation] = useState('');
const [weather, setWeather] = useState(null);
const [hourly, setHourly] = useState([]);
const [loading, setLoading] = useState(false);
const [petMood, setPetMood] = useState('happy');
const [petAnimation, setPetAnimation] = useState(0);
const [screen, setScreen] = useState('main');
const [notes, setNotes] = useState('');
const [savedForecasts, setSavedForecasts] = useState([]);
const [tempUnit, setTempUnit] = useState('F');
const [windUnit, setWindUnit] = useState('mph');
const [stats, setStats] = useState({
checkStreak: 0,
lastCheckDate: null,
sunnyDays: 0,
rainyDays: 0,
snowyDays: 0,
totalChecks: 0
});
// Meteor shower data
const meteorShowers = [
{ name: "Quadrantids", peak: "Jan 3-4", period: "Dec 28 - Jan 12" },
{ name: "Lyrids", peak: "Apr 22-23", period: "Apr 16-25" },
{ name: "Eta Aquarids", peak: "May 6-7", period: "Apr 19 - May 28" },
{ name: "Perseids", peak: "Aug 12-13", period: "Jul 17 - Aug 24" },
{ name: "Orionids", peak: "Oct 21-22", period: "Oct 2 - Nov 7" },
{ name: "Leonids", peak: "Nov 17-18", period: "Nov 6-30" },
{ name: "Geminids", peak: "Dec 13-14", period: "Dec 4-20" }
];
useEffect(() => {
loadSavedForecasts();
loadStats();
}, []);
const loadStats = async () => {
try {
const result = await window.storage.get('weatherpet:stats');
if (result) {
setStats(JSON.parse(result.value));
}
} catch (error) {
console.log('No stats yet');
}
};
const saveStats = async (newStats) => {
try {
await window.storage.set('weatherpet:stats', JSON.stringify(newStats));
setStats(newStats);
} catch (error) {
console.error('Could not save stats');
}
};
const updateStats = (weatherCondition) => {
const today = new Date().toDateString();
let newStats = { ...stats };
// Update streak
if (stats.lastCheckDate === today) {
// Already checked today, don't update streak
} else if (stats.lastCheckDate === new Date(Date.now() - 86400000).toDateString()) {
// Checked yesterday, increase streak
newStats.checkStreak += 1;
} else {
// Streak broken
newStats.checkStreak = 1;
}
newStats.lastCheckDate = today;
newStats.totalChecks += 1;
// Update weather type counters
const condition = weatherCondition.toLowerCase();
if (condition.includes('sunny') || condition.includes('clear')) {
newStats.sunnyDays += 1;
} else if (condition.includes('rain')) {
newStats.rainyDays += 1;
} else if (condition.includes('snow')) {
newStats.snowyDays += 1;
}
saveStats(newStats);
};
const loadSavedForecasts = async () => {
try {
const keys = await window.storage.list('forecast:');
if (keys && keys.keys) {
const forecasts = [];
for (const key of keys.keys) {
const result = await window.storage.get(key);
if (result) {
forecasts.push(JSON.parse(result.value));
}
}
setSavedForecasts(forecasts.sort((a, b) => b.timestamp - a.timestamp));
}
} catch (error) {
console.log('No saved forecasts yet');
}
};
useEffect(() => {
const interval = setInterval(() => {
setPetAnimation(prev => (prev + 1) % 2);
}, 500);
return () => clearInterval(interval);
}, []);
useEffect(() => {
if (!weather) return;
const temp = tempUnit === 'F' ? weather.temp : weather.tempC;
const condition = weather.condition.toLowerCase();
if (condition.includes('rain')) setPetMood('rainy');
else if (condition.includes('snow')) setPetMood('snowy');
else if (condition.includes('cloud')) setPetMood('cloudy');
else if ((tempUnit === 'F' && temp > 80) || (tempUnit === 'C' && temp > 27)) setPetMood('hot');
else if ((tempUnit === 'F' && temp < 32) || (tempUnit === 'C' && temp < 0)) setPetMood('cold');
else setPetMood('happy');
}, [weather, tempUnit]);
const convertTemp = (f) => Math.round((f - 32) * 5 / 9);
const mphToKmh = (mph) => Math.round(mph * 1.60934);
const mphToKnots = (mph) => Math.round(mph * 0.868976);
const fetchWeather = async () => {
if (!location) return;
setLoading(true);
try {
// First, geocode the location
const geoResponse = await fetch(`https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(location)}&count=1&language=en&format=json`);
const geoData = await geoResponse.json();
if (!geoData.results || geoData.results.length === 0) {
alert('Location not found! Try a different city name.');
setLoading(false);
return;
}
const { latitude, longitude, name, country } = geoData.results[0];
// Fetch current weather and hourly forecast
const weatherResponse = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,relative_humidity_2m,apparent_temperature,precipitation,weather_code,cloud_cover,wind_speed_10m,visibility&hourly=temperature_2m,weather_code&temperature_unit=fahrenheit&wind_speed_unit=mph&timezone=auto&forecast_days=1`
);
const weatherData = await weatherResponse.json();
// Convert weather code to condition text
const getConditionFromCode = (code) => {
if (code === 0) return 'Clear';
if (code <= 3) return 'Partly Cloudy';
if (code <= 48) return 'Foggy';
if (code <= 67) return 'Rainy';
if (code <= 77) return 'Snowy';
if (code <= 82) return 'Showers';
if (code <= 86) return 'Snow Showers';
if (code <= 99) return 'Thunderstorm';
return 'Unknown';
};
const currentCondition = getConditionFromCode(weatherData.current.weather_code);
const weather = {
location: `${name}, ${country}`,
temp: Math.round(weatherData.current.temperature_2m),
tempC: Math.round((weatherData.current.temperature_2m - 32) * 5 / 9),
condition: currentCondition,
humidity: weatherData.current.relative_humidity_2m,
windMph: Math.round(weatherData.current.wind_speed_10m),
windKmh: Math.round(weatherData.current.wind_speed_10m * 1.60934),
feelsLike: Math.round(weatherData.current.apparent_temperature),
feelsLikeC: Math.round((weatherData.current.apparent_temperature - 32) * 5 / 9),
icon: weatherData.current.weather_code,
cloudCover: weatherData.current.cloud_cover,
visibility: Math.round(weatherData.current.visibility / 1609.34) // meters to miles
};
setWeather(weather);
updateStats(currentCondition);
// Process hourly data
const now = new Date();
const currentHour = now.getHours();
const hourlyForecasts = [];
for (let i = 0; i < 24; i++) {
const tempF = weatherData.hourly.temperature_2m[i];
hourlyForecasts.push({
time: i,
temp: Math.round(tempF),
tempC: Math.round((tempF - 32) * 5 / 9),
condition: getConditionFromCode(weatherData.hourly.weather_code[i]),
icon: weatherData.hourly.weather_code[i]
});
}
setHourly(hourlyForecasts);
} catch (error) {
console.error('Weather fetch error:', error);
alert('Unable to fetch weather data. Please check your internet connection and try again!');
}
setLoading(false);
};
const saveCurrentForecast = async () => {
if (!weather) return;
const forecast = {
timestamp: Date.now(),
date: new Date().toLocaleDateString(),
weather,
hourly,
notes
};
try {
await window.storage.set(`forecast:${forecast.timestamp}`, JSON.stringify(forecast));
await loadSavedForecasts();
alert('Weather forecast saved! πΎ');
} catch (error) {
console.error('Save error:', error);
alert('Could not save forecast');
}
};
const deleteForecast = async (timestamp) => {
try {
await window.storage.delete(`forecast:${timestamp}`);
await loadSavedForecasts();
} catch (error) {
console.error('Delete error:', error);
}
};
const isGoodStargazing = () => {
if (!weather) return false;
return weather.cloudCover < 30 && weather.visibility > 6;
};
const getUpcomingMeteorShowers = () => {
const today = new Date();
const month = today.getMonth();
const day = today.getDate();
return meteorShowers.filter(shower => {
const peakMonth = new Date(shower.peak + ", 2025").getMonth();
const peakDay = new Date(shower.peak + ", 2025").getDate();
const daysUntil = (peakMonth - month) * 30 + (peakDay - day);
return daysUntil >= -7 && daysUntil <= 30;
});
};
const getWeatherIcon = (condition) => {
const c = condition?.toLowerCase() || '';
if (c.includes('rain')) return