From eeded6c20e3ef51f8afcf2c86390b8b2e80a6658 Mon Sep 17 00:00:00 2001 From: Theo Date: Sat, 23 May 2026 00:31:17 -0300 Subject: [PATCH] feat: adicionar alternancia de tema --- src/App.tsx | 49 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index fbcd492..8a3fb20 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; -import { HeartPulse } from 'lucide-react'; +import { HeartPulse, Moon, Sun } from 'lucide-react'; import { fetchClimaCuidaData } from './api/openMeteo'; import { ErrorBanner } from './components/ErrorBanner'; import { Forecast7Days } from './components/Forecast7Days'; @@ -16,10 +16,12 @@ import { calculateRiskScore } from './lib/riskScore'; import type { LocationOption, ProfileId, WeatherBundle } from './types'; const STORAGE_KEY = 'clima-cuida-preferences'; +type ThemeMode = 'light' | 'dark'; interface StoredPreferences { profile?: ProfileId; location?: LocationOption; + theme?: ThemeMode; } function loadPreferences(): StoredPreferences { @@ -42,6 +44,7 @@ function savePreferences(preferences: StoredPreferences) { export default function App() { const initialPreferences = useMemo(() => loadPreferences(), []); const [profile, setProfile] = useState(initialPreferences.profile ?? 'adult'); + const [theme, setTheme] = useState(initialPreferences.theme ?? 'light'); const [activeLocation, setActiveLocation] = useState( initialPreferences.location ?? MOCK_WEATHER.location, ); @@ -51,6 +54,7 @@ export default function App() { const selectedProfile = USE_PROFILES.find((item) => item.id === profile) ?? USE_PROFILES[0]; const risk = useMemo(() => calculateRiskScore(weather.current, profile), [profile, weather.current]); + const isDarkTheme = theme === 'dark'; const loadWeather = useCallback( async (location: LocationOption) => { @@ -60,19 +64,19 @@ export default function App() { const nextWeather = await fetchClimaCuidaData(location); setWeather(nextWeather); setActiveLocation(location); - savePreferences({ profile, location }); + savePreferences({ profile, location, theme }); } catch { setWeather({ ...MOCK_WEATHER, fetchedAt: new Date().toISOString(), source: 'mock', }); - setError('A API não respondeu agora. Exibindo dados de exemplo para manter a leitura do dia utilizável.'); + setError('A API nao respondeu agora. Exibindo dados de exemplo para manter a leitura do dia utilizavel.'); } finally { setIsLoading(false); } }, - [profile], + [profile, theme], ); useEffect(() => { @@ -80,16 +84,24 @@ export default function App() { }, []); useEffect(() => { - savePreferences({ profile, location: activeLocation }); - }, [profile, activeLocation]); + document.documentElement.dataset.theme = theme; + }, [theme]); + + useEffect(() => { + savePreferences({ profile, location: activeLocation, theme }); + }, [profile, activeLocation, theme]); function handleProfileChange(nextProfile: ProfileId) { setProfile(nextProfile); } + function handleToggleTheme() { + setTheme((currentTheme) => (currentTheme === 'light' ? 'dark' : 'light')); + } + function handleUseCurrentLocation() { if (!navigator.geolocation) { - setError('Este navegador não oferece geolocalização. Busque uma cidade pelo nome.'); + setError('Este navegador nao oferece geolocalizacao. Busque uma cidade pelo nome.'); return; } @@ -98,7 +110,7 @@ export default function App() { (position) => { const location: LocationOption = { id: 'browser-location', - name: 'Sua localização', + name: 'Sua localizacao', latitude: position.coords.latitude, longitude: position.coords.longitude, }; @@ -106,7 +118,7 @@ export default function App() { }, () => { setIsLoading(false); - setError('Não foi possível acessar sua localização. Você pode pesquisar uma cidade manualmente.'); + setError('Nao foi possivel acessar sua localizacao. Voce pode pesquisar uma cidade manualmente.'); }, { enableHighAccuracy: false, timeout: 9000, maximumAge: 10 * 60 * 1000 }, ); @@ -127,16 +139,27 @@ export default function App() { onSelectLocation={loadWeather} onUseCurrentLocation={handleUseCurrentLocation} /> - +
+ + +
{error && loadWeather(activeLocation)} />}
-

Orientação geral, não recomendação médica

+

Orientacao geral, nao recomendacao medica

- Leitura combinada de clima, UV e qualidade do ar para decidir saída, exercício, deslocamento - e proteção diária. + Leitura combinada de clima, UV e qualidade do ar para decidir saida, exercicio, deslocamento + e protecao diaria.