diff --git a/.env b/.env deleted file mode 100644 index 66a0b74..0000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -VITE_OPEN_WEATHER_API_KEY=955915eda8981d86ee32efec8d5251c3 \ No newline at end of file diff --git a/.gitignore b/.gitignore index a547bf3..7ceb59f 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ dist-ssr *.njsproj *.sln *.sw? +.env diff --git a/README.md b/README.md index f768e33..eef08af 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,327 @@ -# React + Vite +# PRA#03_React-EmmaAlonsoMcCoy-WeatherApp -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +## Overview -Currently, two official plugins are available: +This document serves as a guide and log for the frontend development of the WeatherApp project. -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +--- + +# Project Backlog + +--- + +# ** Estimated Time for Tasks** + +| Task | Estimated Time | Actual Time | Impediments | New Concepts | +| ------------------------------------------ | -------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------- | +| Setup Git & README | 30 min | 2 hours | - | - | +| Create `.gitignore` and `.env` | 15 min | 15 minutes | - | .env file | +| Study OpenWeather API | 1 hour | 1 hour | API limits? Built-in geolocation has been deprecated | Fetching JSON | +| Understanding useEffect for API Calls | 45 min | 45 min | - | Side effects
 Not efficient for event handlers like onClick | +| Understanding Mapping Arrays | 15 min | 15 min | - | Array methods | +| Define Component Tree | 30 min | 30 min | Had to redesign according to new tasks | - | +| Create Wireframe | 1 hour | 1:30 hour | Had to redesign according to new tasks | Excalidraw tool | +| Set up React Project Folders | 30 min | 30 min | - | - | +| Implement Router for multi-page navigation | 1 hour | 2 hours | Routing concept not understood properly | | +| Create Weather page | | | | | +| Implement Search Component | 1 hours | 1 hour | - | useState, onChange | +| Implement Current Weather Display | 2 hours | 1 hour | - | API calls, useEffect | +| Implement 5-Day Forecast Component | 2 hours | 1 hour | - | Mapping data | +| Implement Favourite Locations | 1:30 hours | 1 hour | - | Async/Await | +| Implement Map page | 1:30 hours | 1:30 hours | Obsolete styling conflict in Material UI (v5) caused by missing ThemeProvider, and ES modules error in Leaflet from incompatible require() usage | Error handling | +| Use Material UI for Styling | 2 hours | X | - | Component library | +| **Total** | **~16 hours** | **X hours** | - | Tasks | + +--- + +# OpenWeather API Documentation + +> This project will use the Built-in API request by city name. However, please note that [built-in geocoder](https://openweathermap.org/current#geocoding) has been deprecated. Although it is still available for use, bug fixing and updates are no longer available for this functionality. + +### Authentication + +To access OpenWeather API endpoints, you need an API key. + +### Steps to Use the API Key: + +1. Sign up at [OpenWeather](https://home.openweathermap.org/users/sign_up). + +2. Verify your email and log in. + +3. Navigate to "API keys" in your account settings. + +4. Copy your generated API key. + +5. Paste it in your .env file and make sure the .env file is included in your .gitignore + + ```properties + VITE_OPEN_WEATHER_API_KEY=[your API key] + ``` + +### Base URL + +All API requests should be made to: + +``` +https://api.openweathermap.org/data/2.5/ +``` + +## Endpoints + +### 1. Current Weather Data + +Fetch real-time weather conditions for a city. + +**Endpoint:** + +``` +GET /weather +``` + +**Query Parameters:** + +| Parameter | Type | Description | +| --------- | ------ | ------------------------------------------------------------------ | +| q | string | City name (e.g., `q=London`). | +| appid | string | Your API key. | +| units | string | Metric (`metric`), Imperial (`imperial`), or Standard (`default`). | + +**Example Request:** + +``` +GET https://api.openweathermap.org/data/2.5/weather?q=London&appid=YOUR_API_KEY&units=metric +``` + +**Example Response:** + +```json +{ + "coord": { + "lon": 7.367, + "lat": 45.133 + }, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10d" + } + ], + "base": "stations", + "main": { + "temp": 284.2, + "feels_like": 282.93, + "temp_min": 283.06, + "temp_max": 286.82, + "pressure": 1021, + "humidity": 60, + "sea_level": 1021, + "grnd_level": 910 + }, + "visibility": 10000, + "wind": { + "speed": 4.09, + "deg": 121, + "gust": 3.47 + }, + "rain": { + "1h": 2.73 + }, + "clouds": { + "all": 83 + }, + "dt": 1726660758, + "sys": { + "type": 1, + "id": 6736, + "country": "IT", + "sunrise": 1726636384, + "sunset": 1726680975 + }, + "timezone": 7200, + "id": 3165523, + "name": "Province of Turin", + "cod": 200 +} +``` + +**JSON format API response fields** + +- `coord` + - `coord.lon` Longitude of the location + - `coord.lat` Latitude of the location +- `weather` (more info [Weather condition codes](https://openweathermap.org/weather-conditions)) + - `weather.id` Weather condition id + - `weather.main` Group of weather parameters (Rain, Snow, Clouds etc.) + - `weather.description` Weather condition within the group. Please find more [here.](https://openweathermap.org/current#list) You can get the output in your language. [Learn more](https://openweathermap.org/current#multi) + - `weather.icon` Weather icon id +- `base` Internal parameter +- `main` + - `main.temp` Temperature. Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit + - `main.feels_like` Temperature. This temperature parameter accounts for the human perception of weather. Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit + - `main.pressure` Atmospheric pressure on the sea level, hPa + - `main.humidity` Humidity, % + - `main.temp_min` Minimum temperature at the moment. This is minimal currently observed temperature (within large megalopolises and urban areas). Please find more info [here.](https://openweathermap.org/current#min) Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit + - `main.temp_max` Maximum temperature at the moment. This is maximal currently observed temperature (within large megalopolises and urban areas). Please find more info [here.](https://openweathermap.org/current#min) Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit + - `main.sea_level` Atmospheric pressure on the sea level, hPa + - `main.grnd_level` Atmospheric pressure on the ground level, hPa +- `visibility` Visibility, meter. The maximum value of the visibility is 10 km +- `wind` + - `wind.speed` Wind speed. Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour + - `wind.deg` Wind direction, degrees (meteorological) + - `wind.gust` Wind gust. Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour +- `clouds` + - `clouds.all` Cloudiness, % +- `rain` + - `1h`(where available)Precipitation, mm/h. Please note that only mm/h as units of measurement are available for this parameter +- `snow` + - `1h`(where available) Precipitation, mm/h. Please note that only mm/h as units of measurement are available for this parameter +- `dt` Time of data calculation, unix, UTC +- `sys` + - `sys.type` Internal parameter + - `sys.id` Internal parameter + - `sys.message` Internal parameter + - `sys.country` Country code (GB, JP etc.) + - `sys.sunrise` Sunrise time, unix, UTC + - `sys.sunset` Sunset time, unix, UTC +- `timezone` Shift in seconds from UTC +- `id` City ID. Please note that built-in geocoder functionality has been deprecated. Learn more [here](https://openweathermap.org/current#builtin) +- `name` City name. Please note that built-in geocoder functionality has been deprecated. Learn more [here](https://openweathermap.org/current#builtin) +- `cod` Internal parameter + +--- + +### 2. 5-Day Forecast + +Retrieve a forecast for the next 5 days at 3-hour intervals. + +**Endpoint:** + +``` +GET /forecast +``` + +**Query Parameters:** (Same as Current Weather Data) + +**Example Request:** + +``` +GET https://api.openweathermap.org/data/2.5/forecast?q=London&appid=YOUR_API_KEY&units=metric +``` + +--- + +## Response Codes + +| Code | Meaning | +| ---- | ----------------- | +| 200 | Success | +| 401 | Invalid API key | +| 404 | City not found | +| 429 | Too many requests | + +--- + +# Component Tree v1.0 + +```mermaid +graph TD +A[WeatherApp] --> B[Router] +B --> C[Home] +B --> D[Weather] +B --> E[Map] +B --> F[About] +D --> G[SearchBar] +D --> H[CurrentWeather] +D --> I[5-DayForecast] +D --> J[Favorites] +E --> K[LeafletMap] +E --> L[MapMarkers] +``` + +--- + +# Wireframe (v1.0) + +![](/home/emma/MyProjects/DAW/weatherApp/_PRA/Multipage_Wireframe_Excalidraw.png) + +# Error Documentation and Solutions + +### Error: `MUI: The 'styles' argument is invalid` & `ReferenceError: require is not defined` + +**Corresponding Task:** Integrate Leaflet map with Material UI v5 + +**Description:** + +Two concurrent errors: + +1. Material UI theme context missing due to deprecated `makeStyles` implementation + +2. Leaflet icon imports failing due to CommonJS `require()` in ES modules environment + +**Error Trace:** + +- **Component:** `MapComponent` + +- **File:** `MapComponent.jsx` + +- **Line:** 19 (MUI error) | 20 (Leaflet error) + +- **Stack Trace:** + + - `TypeError: theme.spacing is not a function` + + - `Uncaught ReferenceError: require is not defined` + +**Possible Causes:** + +1. Using legacy `@mui/styles` instead of modern Emotion/styled API + +2. Missing ThemeProvider wrapper in parent components + +3. Incorrect Leaflet asset loading in module bundlers (Vite/Webpack) + +**Solution:** + +```jsx +// Fixed Material UI implementation +import { styled } from '@mui/material/styles'; + +const MapContainerStyled = styled(MapContainer)(({ theme }) => ({ + height: "400px", + width: "100%", +})); + +// Fixed Leaflet imports +import markerIcon from 'leaflet/dist/images/marker-icon.png'; +import markerShadow from 'leaflet/dist/images/marker-shadow.png'; + +// Icon configuration +delete L.Icon.Default.prototype._getIconUrl; +L.Icon.Default.mergeOptions({ + iconUrl: markerIcon, + shadowUrl: markerShadow, +}); + +``` + +**Explanation:** + +1. **Material UI Fix:** Replaced deprecated `makeStyles` with `styled()` API to properly access theme context + +2. **Leaflet Fix:** Converted CommonJS `require()` to ES module static imports and configured icon paths + +3. **Bundler Compatibility:** Added file-loader/webpack aliases to handle Leaflet's PNG assets + +--- + +## Future Improvements + +- [ ] Improve **loading states** while fetching data + +- [ ] Allow **temperature unit conversion (°C ⇄ °F)** + +- [ ] Include **dark mode** + +--- diff --git a/_PRA/ComponentTree_Mermaid.svg b/_PRA/ComponentTree_Mermaid.svg new file mode 100644 index 0000000..f24022a --- /dev/null +++ b/_PRA/ComponentTree_Mermaid.svg @@ -0,0 +1 @@ +

WeatherApp

Search Bar

Current Weather

5-day Forecast

Location

Temperature

Humidity

Wind Speed

Day 1

Day 2

Day 3

Day 4

Day 5

Input Field

Submit Button

\ No newline at end of file diff --git a/_PRA/Multipage_Wireframe_Excalidraw.png b/_PRA/Multipage_Wireframe_Excalidraw.png new file mode 100644 index 0000000..62794a6 Binary files /dev/null and b/_PRA/Multipage_Wireframe_Excalidraw.png differ diff --git a/_PRA/SPA_Wireframe_Excalidraw.png b/_PRA/SPA_Wireframe_Excalidraw.png new file mode 100644 index 0000000..0a81d8b Binary files /dev/null and b/_PRA/SPA_Wireframe_Excalidraw.png differ diff --git a/package-lock.json b/package-lock.json index a87405c..2a0c413 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,17 @@ "name": "weatherapp", "version": "0.0.0", "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mui/icons-material": "^5.14.0", + "@mui/material": "^5.14.0", + "@mui/styles": "^6.4.1", "axios": "^1.7.9", + "leaflet": "^1.9.4", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "react-leaflet": "^5.0.0", + "react-router-dom": "^7.2.0" }, "devDependencies": { "@eslint/js": "^9.19.0", @@ -25,6 +33,294 @@ "vite": "^6.1.0" } }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", + "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", + "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.9" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz", + "integrity": "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", + "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz", + "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", + "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", + "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.24.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", @@ -629,6 +925,415 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "5.16.14", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.14.tgz", + "integrity": "sha512-sbjXW+BBSvmzn61XyTMun899E7nGPTXwqD9drm1jBUAvWEhJpPFIRxwQQiATWZnd9rvdxtnhhdsDxEGWI0jxqA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "5.16.14", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.16.14.tgz", + "integrity": "sha512-heL4S+EawrP61xMXBm59QH6HODsu0gxtZi5JtnXF2r+rghzyU/3Uftlt1ij8rmJh+cFdKTQug1L9KkZB5JgpMQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "5.16.14", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.14.tgz", + "integrity": "sha512-eSXQVCMKU2xc7EcTxe/X/rC9QsV2jUe8eLM3MUCPYbo6V52eCE436akRIvELq/AqZpxx2bwkq7HC0cRhLB+yaw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/core-downloads-tracker": "^5.16.14", + "@mui/system": "^5.16.14", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.14", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.10", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.0.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", + "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==", + "license": "MIT" + }, + "node_modules/@mui/private-theming": { + "version": "5.16.14", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.14.tgz", + "integrity": "sha512-12t7NKzvYi819IO5IapW2BcR33wP/KAVrU8d7gLhGHoAmhDxyXlRoKiRij3TOD8+uzk0B6R9wHUNKi4baJcRNg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.16.14", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "5.16.14", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.14.tgz", + "integrity": "sha512-UAiMPZABZ7p8mUW4akDV6O7N3+4DatStpXMZwPlt+H/dA0lt67qawN021MNND+4QTpjaiMYxbhKZeQcyWCbuKw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@emotion/cache": "^11.13.5", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/styles": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/styles/-/styles-6.4.6.tgz", + "integrity": "sha512-mz5qEJbyV9aRyBUhy2wJ2gJBzU6/yrEPUgaWs53PUrDLk9fFcTXWiZKJy7vZUBdgCNL6thbqW2B1ms98lSeWFA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@emotion/hash": "^0.9.2", + "@mui/private-theming": "^6.4.6", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.6", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "hoist-non-react-statics": "^3.3.2", + "jss": "^10.10.0", + "jss-plugin-camel-case": "^10.10.0", + "jss-plugin-default-unit": "^10.10.0", + "jss-plugin-global": "^10.10.0", + "jss-plugin-nested": "^10.10.0", + "jss-plugin-props-sort": "^10.10.0", + "jss-plugin-rule-value-function": "^10.10.0", + "jss-plugin-vendor-prefixer": "^10.10.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styles/node_modules/@mui/private-theming": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.6.tgz", + "integrity": "sha512-T5FxdPzCELuOrhpA2g4Pi6241HAxRwZudzAuL9vBvniuB5YU82HCmrARw32AuCiyTfWzbrYGGpZ4zyeqqp9RvQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.4.6", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styles/node_modules/@mui/utils": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.6.tgz", + "integrity": "sha512-43nZeE1pJF2anGafNydUcYFPtHwAqiBiauRtaMvurdrZI3YrUjHkAu43RBsxef7OFtJMXGiHFvq43kb7lig0sA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/types": "^7.2.21", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styles/node_modules/react-is": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", + "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==", + "license": "MIT" + }, + "node_modules/@mui/system": { + "version": "5.16.14", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.14.tgz", + "integrity": "sha512-KBxMwCb8mSIABnKvoGbvM33XHyT+sN0BzEBG+rsSc0lLQGzs7127KWkCA6/H8h6LZ00XpBEME5MAj8mZLiQ1tw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.16.14", + "@mui/styled-engine": "^5.16.14", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.14", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.21", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz", + "integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "5.16.14", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.14.tgz", + "integrity": "sha512-wn1QZkRzSmeXD1IguBVvJJHV3s6rxJrfb6YuC9Kk6Noh9f8Fb54nUs5JRkKm+BOerRhj5fLg05Dhx/H3Ofb8Mg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/types": "^7.2.15", + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils/node_modules/react-is": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", + "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==", + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@react-leaflet/core": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-3.0.0.tgz", + "integrity": "sha512-3EWmekh4Nz+pGcr+xjf0KNyYfC3U2JjnkWsh0zcqaexYqmmB5ZhH37kz41JXGmKzpaMZCnPofBBm64i+YrEvGQ==", + "license": "Hippocratic-2.1", + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.34.6", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz", @@ -1089,6 +1794,12 @@ "@swc/counter": "^0.1.3" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -1101,11 +1812,22 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.0.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz", "integrity": "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==", - "dev": true, "dependencies": { "csstype": "^3.0.2" } @@ -1119,6 +1841,15 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@vitejs/plugin-react-swc": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.8.0.tgz", @@ -1357,6 +2088,41 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-macros/node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1424,7 +2190,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -1445,6 +2210,15 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1480,6 +2254,46 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1494,11 +2308,20 @@ "node": ">= 8" } }, + "node_modules/css-vendor": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", + "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.3", + "is-in-browser": "^1.0.2" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/data-view-buffer": { "version": "1.0.2", @@ -1555,7 +2378,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, "dependencies": { "ms": "^2.1.3" }, @@ -1628,6 +2450,16 @@ "node": ">=0.10.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1642,6 +2474,15 @@ "node": ">= 0.4" } }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-abstract": { "version": "1.23.9", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", @@ -1849,7 +2690,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "engines": { "node": ">=10" }, @@ -2086,6 +2926,12 @@ "node": ">=16.0.0" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2186,7 +3032,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2405,7 +3250,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -2413,6 +3257,21 @@ "node": ">= 0.4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hyphenate-style-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", + "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2426,7 +3285,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -2478,6 +3336,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, "node_modules/is-async-function": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", @@ -2544,7 +3408,6 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, "dependencies": { "hasown": "^2.0.2" }, @@ -2642,6 +3505,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-in-browser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", + "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==", + "license": "MIT" + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -2838,8 +3707,7 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -2853,12 +3721,30 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2871,6 +3757,96 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/jss": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.10.0.tgz", + "integrity": "sha512-cqsOTS7jqPsPMjtKYDUpdFC0AbhYFLTcuGRqymgmdJIeQ8cH7+AgX7YSgQy79wXloZq2VvATYxUOUQEvS1V/Zw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.3.1", + "csstype": "^3.0.2", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/jss" + } + }, + "node_modules/jss-plugin-camel-case": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.10.0.tgz", + "integrity": "sha512-z+HETfj5IYgFxh1wJnUAU8jByI48ED+v0fuTuhKrPR+pRBYS2EDwbusU8aFOpCdYhtRc9zhN+PJ7iNE8pAWyPw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.3.1", + "hyphenate-style-name": "^1.0.3", + "jss": "10.10.0" + } + }, + "node_modules/jss-plugin-default-unit": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.10.0.tgz", + "integrity": "sha512-SvpajxIECi4JDUbGLefvNckmI+c2VWmP43qnEy/0eiwzRUsafg5DVSIWSzZe4d2vFX1u9nRDP46WCFV/PXVBGQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0" + } + }, + "node_modules/jss-plugin-global": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.10.0.tgz", + "integrity": "sha512-icXEYbMufiNuWfuazLeN+BNJO16Ge88OcXU5ZDC2vLqElmMybA31Wi7lZ3lf+vgufRocvPj8443irhYRgWxP+A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0" + } + }, + "node_modules/jss-plugin-nested": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.10.0.tgz", + "integrity": "sha512-9R4JHxxGgiZhurDo3q7LdIiDEgtA1bTGzAbhSPyIOWb7ZubrjQe8acwhEQ6OEKydzpl8XHMtTnEwHXCARLYqYA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0", + "tiny-warning": "^1.0.2" + } + }, + "node_modules/jss-plugin-props-sort": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.10.0.tgz", + "integrity": "sha512-5VNJvQJbnq/vRfje6uZLe/FyaOpzP/IH1LP+0fr88QamVrGJa0hpRRyAa0ea4U/3LcorJfBFVyC4yN2QC73lJg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0" + } + }, + "node_modules/jss-plugin-rule-value-function": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.10.0.tgz", + "integrity": "sha512-uEFJFgaCtkXeIPgki8ICw3Y7VMkL9GEan6SqmT9tqpwM+/t+hxfMUdU4wQ0MtOiMNWhwnckBV0IebrKcZM9C0g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0", + "tiny-warning": "^1.0.2" + } + }, + "node_modules/jss-plugin-vendor-prefixer": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.10.0.tgz", + "integrity": "sha512-UY/41WumgjW8r1qMCO8l1ARg7NHnfRVWRhZ2E2m0DMYsr2DD91qIXLyNhiX83hHswR7Wm4D+oDYNC1zWCJWtqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.3.1", + "css-vendor": "^2.0.8", + "jss": "10.10.0" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -2895,6 +3871,12 @@ "json-buffer": "3.0.1" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2908,6 +3890,12 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2933,7 +3921,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -2984,8 +3971,7 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/nanoid": { "version": "3.3.8", @@ -3015,7 +4001,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3179,7 +4164,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -3187,6 +4171,24 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3208,14 +4210,21 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/possible-typed-array-names": { "version": "1.1.0", @@ -3267,7 +4276,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -3310,8 +4318,77 @@ "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-leaflet": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-5.0.0.tgz", + "integrity": "sha512-CWbTpr5vcHw5bt9i4zSlPEVQdTVcML390TjeDG0cK59z1ylexpqC6M1PJFjV8jD7CF+ACBFsLIDs6DRMoLEofw==", + "license": "Hippocratic-2.1", + "dependencies": { + "@react-leaflet/core": "^3.0.0" + }, + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + }, + "node_modules/react-router": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.2.0.tgz", + "integrity": "sha512-fXyqzPgCPZbqhrk7k3hPcCpYIlQ2ugIXDboHUzhJISFVy2DEPsmHgN588MyGmkIOv3jDgNfUE3kJi83L28s/LQ==", + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.2.0.tgz", + "integrity": "sha512-cU7lTxETGtQRQbafJubvZKHEn5izNABxZhBY0Jlzdv0gqQhCPQt2J8aN5ZPjS6mQOXn5NnirWNh+FpE8TTYN0Q==", + "license": "MIT", + "dependencies": { + "react-router": "7.2.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", @@ -3335,6 +4412,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -3376,7 +4459,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -3485,6 +4567,12 @@ "semver": "bin/semver.js" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -3624,6 +4712,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3738,6 +4835,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3754,7 +4857,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -3762,6 +4864,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", + "license": "MIT" + }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4054,6 +5168,21 @@ "node": ">=0.10.0" } }, + "node_modules/yaml": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 58fee0b..5a757ab 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,17 @@ "preview": "vite preview" }, "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mui/icons-material": "^5.14.0", + "@mui/material": "^5.14.0", + "@mui/styles": "^6.4.1", "axios": "^1.7.9", + "leaflet": "^1.9.4", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "react-leaflet": "^5.0.0", + "react-router-dom": "^7.2.0" }, "devDependencies": { "@eslint/js": "^9.19.0", diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 4014cd7..0000000 --- a/src/App.css +++ /dev/null @@ -1,7 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - diff --git a/src/App.jsx b/src/App.jsx index dcce23f..a2acaae 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,11 +1,27 @@ -import Weather from './Weather' +import { BrowserRouter, Routes, Route } from "react-router-dom"; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import MainLayout from './layouts/MainLayout'; +import Home from './pages/Home'; +import WeatherPage from './pages/WeatherPage'; +import About from './pages/About'; +import MapPage from './pages/MapPage'; +import './styles/App.css'; +const theme = createTheme(); export default function App() { return ( - <> - - + + + + }> + } /> + } /> + } /> + } /> + + + + ); } - diff --git a/src/components/CurrentWeather.jsx b/src/components/CurrentWeather.jsx new file mode 100644 index 0000000..700c980 --- /dev/null +++ b/src/components/CurrentWeather.jsx @@ -0,0 +1,44 @@ +import { Card, CardContent, Typography, IconButton } from '@mui/material'; +import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder'; +import FavoriteIcon from '@mui/icons-material/Favorite'; +import styles from '../styles/CurrentWeather.module.css'; + +export default function CurrentWeather({ data, isFavorite, onToggleFavorite }) { + return ( + + +
+ + {data.name}, {data.sys.country} + + + {isFavorite ? : } + +
+ +
+ {data.weather[0].description} + {Math.round(data.main.temp)}°C +
+ + + {data.weather[0].main} ({data.weather[0].description}) + + +
+
+ Feels like: {Math.round(data.main.feels_like)}°C + Humidity: {data.main.humidity}% +
+
+ Wind: {data.wind.speed} m/s + Pressure: {data.main.pressure} hPa +
+
+
+
+ ); +} diff --git a/src/components/FavoritesManager.jsx b/src/components/FavoritesManager.jsx new file mode 100644 index 0000000..c8bde23 --- /dev/null +++ b/src/components/FavoritesManager.jsx @@ -0,0 +1,49 @@ +import { List, ListItem, ListItemText, IconButton, Paper, Typography, Box } from '@mui/material'; +import DeleteIcon from '@mui/icons-material/Delete'; +import styles from '../styles/FavoritesManager.module.css'; + +export default function FavoritesManager({ favorites, onRemove, onSelect }) { + return ( + + Favorite Cities + + {favorites.map((city) => ( + onRemove(city.id)} + title="Remove favorite" + > + + + } + > + onSelect(city.name)} className={styles.weatherSummary}> +
+ + {city.name}, {city.sys.country} + + {city.weather[0].description} +
+ +
+ + {Math.round(city.main.temp)}°C + + + {city.weather[0].main} + +
+
+
+ ))} +
+
+ ); +} diff --git a/src/components/Forecast.jsx b/src/components/Forecast.jsx new file mode 100644 index 0000000..cb975f6 --- /dev/null +++ b/src/components/Forecast.jsx @@ -0,0 +1,35 @@ +import { Card, CardContent, Typography, Grid } from '@mui/material'; +import styles from '../styles/Forecast.module.css'; + +export default function Forecast({ data }) { + return ( +
+ 5-Day Forecast + + {data.map((item, index) => ( + + + + + {new Date(item.dt_txt).toLocaleDateString('en-US', { + weekday: 'short' + })} + + {item.weather[0].description} + + {Math.round(item.main.temp)}°C + + + {item.weather[0].main} + + + + + ))} + +
+ ); +} diff --git a/src/components/MapComponent.jsx b/src/components/MapComponent.jsx new file mode 100644 index 0000000..8e882ba --- /dev/null +++ b/src/components/MapComponent.jsx @@ -0,0 +1,53 @@ +import { Paper, Typography, styled } from "@mui/material"; +import { MapContainer, Marker, Popup, TileLayer } from "react-leaflet"; +import L from 'leaflet'; +import 'leaflet/dist/leaflet.css'; + +// Importa las imágenes directamente +import markerIcon2x from 'leaflet/dist/images/marker-icon-2x.png'; +import markerIcon from 'leaflet/dist/images/marker-icon.png'; +import markerShadow from 'leaflet/dist/images/marker-shadow.png'; + +// Configuración de estilos con MUI v5 +const MapContainerStyled = styled(MapContainer)(({ theme }) => ({ + height: "400px", + width: "100%", + marginTop: theme.spacing(2), +})); + +// Configuración de iconos +delete L.Icon.Default.prototype._getIconUrl; +L.Icon.Default.mergeOptions({ + iconRetinaUrl: markerIcon2x, + iconUrl: markerIcon, + shadowUrl: markerShadow, +}); + +const MapComponent = () => { + const position = [41.3851, 2.1734]; + + return ( + + + Barcelona Central HQ + + + + + + Oficinas centrales
Barcelona +
+
+
+
+ ); +}; + +export default MapComponent; diff --git a/src/components/SearchBar.jsx b/src/components/SearchBar.jsx new file mode 100644 index 0000000..ef8b3d3 --- /dev/null +++ b/src/components/SearchBar.jsx @@ -0,0 +1,25 @@ +import { TextField, Button, Box } from '@mui/material'; +import styles from '../styles/SearchBar.module.css'; + +export default function SearchBar({ city, setCity, onSearch, loading }) { + return ( + + setCity(e.target.value)} + disabled={loading} + fullWidth + /> + + + ); +} diff --git a/src/components/Weather.jsx b/src/components/Weather.jsx new file mode 100644 index 0000000..777e8a2 --- /dev/null +++ b/src/components/Weather.jsx @@ -0,0 +1,100 @@ +import { useState, useEffect } from "react"; +import axios from "axios"; + +const containerStyle = { + width: "100%", + margin: "50px auto", // Adds 50px margin to the top and centers horizontally + padding: "20px", + border: "1px solid #ccc", + borderRadius: "8px", + boxShadow: "0 2px 5px rgba(0, 0, 0, 0.1)", + backgroundColor: "#f9f9f9", +}; + +export default function Weather() { + const [city, setCity] = useState(""); + const [weather, setWeather] = useState(null); + const [forecast, setForecast] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + + const fetchWeatherData = async () => { + if (!city) return; + setLoading(true); + setError(""); + try { + + const apiKey = import.meta.env.VITE_OPEN_WEATHER_API_KEY; + + const currentWeatherResponse = await axios.get( + `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric` + ); + setWeather(currentWeatherResponse.data); + console.log(currentWeatherResponse); + + const forecastResponse = await axios.get( + `https://api.openweathermap.org/data/2.5/forecast?q=${city}&appid=${apiKey}&units=metric` + ); + setForecast( + forecastResponse.data.list.filter((_, index) => index % 8 === 0) + ); + console.log(forecastResponse); + } catch (err) { + setError("Failed to fetch weather data. Please try again."); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchWeatherData(); + }, []); // Fetch default data on load if needed + + return ( +
+

Weather Tracker

+
+ setCity(e.target.value)} + />{" "} + +
+ {loading &&

Loading...

} + {error &&

{error}

} + {weather && ( +
+

Current Weather in {weather.name}

+

+ Temperature:{" "} + + {" "} + {weather.main.temp} + + °C +

+

Condition: {weather.weather[0].description}

+
+ )} + {forecast.length > 0 && ( +
+

5-Day Forecast

+ {forecast.map((day, index) => ( +
+

Date: {new Date(day.dt_txt).toLocaleDateString()}

+

+ Temp:{day.main.temp} °C +

+

Condition: {day.weather[0].description}

+
+
+ ))} +
+ )} +
+ ); +} + + diff --git a/src/index.css b/src/index.css deleted file mode 100644 index d8d3e83..0000000 --- a/src/index.css +++ /dev/null @@ -1,85 +0,0 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - justify-content: center; - align-items: center; - min-height: 100vh; - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - color-scheme: light dark; - background-color: #DCDCDC; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -input { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #rgb(238, 236, 236) - cursor: pointer; - -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/src/layouts/MainLayout.jsx b/src/layouts/MainLayout.jsx new file mode 100644 index 0000000..da5c2b3 --- /dev/null +++ b/src/layouts/MainLayout.jsx @@ -0,0 +1,34 @@ +import { Link, Outlet } from "react-router-dom"; +import { AppBar, Toolbar, Typography, Button, Container } from "@mui/material"; +import { styled } from "@mui/system"; + +const StyledLink = styled(Link)({ + color: "#4b6cb7", + textDecoration: "none", + marginLeft: "20px", + "&:hover": { color: "#FFFFFF" }, +}); + +export default function MainLayout() { + return ( + <> + + + + Weather App + + + + + + + + + + + + ); +} diff --git a/src/main.jsx b/src/main.jsx index b9a1a6d..d91ba14 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,6 +1,6 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' -import './index.css' +import './styles/index.css' import App from './App.jsx' createRoot(document.getElementById('root')).render( diff --git a/src/pages/About.jsx b/src/pages/About.jsx new file mode 100644 index 0000000..6205f41 --- /dev/null +++ b/src/pages/About.jsx @@ -0,0 +1,8 @@ +export default function About(){ + return ( + <> +

About

+

Welcome to our wonderful About page.

+ + ) +}; \ No newline at end of file diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx new file mode 100644 index 0000000..abc6fe1 --- /dev/null +++ b/src/pages/Home.jsx @@ -0,0 +1,9 @@ +export default function Home() { + return ( +
+

Welcome to Weather App

+

Search for weather information in any city worldwide

+
+ ); + } + \ No newline at end of file diff --git a/src/pages/MapPage.jsx b/src/pages/MapPage.jsx new file mode 100644 index 0000000..2f82251 --- /dev/null +++ b/src/pages/MapPage.jsx @@ -0,0 +1,10 @@ +import MapComponent from "../components/MapComponent.jsx" + +export default function MapPage(){ + return ( + <> +

Barcelona Weather Map

+ + + ) +}; \ No newline at end of file diff --git a/src/pages/WeatherPage.jsx b/src/pages/WeatherPage.jsx new file mode 100644 index 0000000..955bd3d --- /dev/null +++ b/src/pages/WeatherPage.jsx @@ -0,0 +1,101 @@ +import { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import axios from 'axios'; +import SearchBar from '../components/SearchBar.jsx'; +import CurrentWeather from '../components/CurrentWeather.jsx'; +import Forecast from '../components/Forecast.jsx'; +import FavoritesManager from '../components/FavoritesManager.jsx'; +import styles from '../styles/WeatherPage.module.css'; + +export default function WeatherPage() { + const [city, setCity] = useState(''); + const [weather, setWeather] = useState(null); + const [forecast, setForecast] = useState([]); + const [favorites, setFavorites] = useState(() => { + return JSON.parse(localStorage.getItem('weatherFavorites')) || []; + }); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const navigate = useNavigate(); + + const fetchWeatherData = async (searchCity) => { + if (!searchCity) return; + setLoading(true); + setError(''); + + try { + const apiKey = import.meta.env.VITE_OPEN_WEATHER_API_KEY; + + const [currentRes, forecastRes] = await Promise.all([ + axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${searchCity}&appid=${apiKey}&units=metric`), + axios.get(`https://api.openweathermap.org/data/2.5/forecast?q=${searchCity}&appid=${apiKey}&units=metric`) + ]); + + setWeather(currentRes.data); + setForecast(forecastRes.data.list.filter((_, i) => i % 8 === 0)); + navigate('/weather'); + } catch (err) { + setError('Failed to fetch weather data. Please check the city name.'); + setWeather(null); + setForecast([]); + } finally { + setLoading(false); + } + }; + + const handleAddFavorite = async () => { + if (!weather) return; + + try { + const apiKey = import.meta.env.VITE_OPEN_WEATHER_API_KEY; + const response = await axios.get( + `https://api.openweathermap.org/data/2.5/weather?q=${weather.name}&appid=${apiKey}&units=metric` + ); + + const newFavorite = response.data; + + if (!favorites.some(f => f.id === newFavorite.id)) { + const updatedFavorites = [...favorites, newFavorite]; + setFavorites(updatedFavorites); + localStorage.setItem('weatherFavorites', JSON.stringify(updatedFavorites)); + } + } catch (err) { + console.error('Error saving favorite:', err); + } + }; + + const handleRemoveFavorite = (cityId) => { + const updatedFavorites = favorites.filter(f => f.id !== cityId); + setFavorites(updatedFavorites); + localStorage.setItem('weatherFavorites', JSON.stringify(updatedFavorites)); + }; + + return ( +
+ fetchWeatherData(city)} + loading={loading} + /> + + {error &&

{error}

} + + {weather && ( + f.id === weather.id)} + onToggleFavorite={handleAddFavorite} + /> + )} + + {forecast.length > 0 && } + + +
+ ); +} diff --git a/src/styles/App.css b/src/styles/App.css new file mode 100644 index 0000000..0b436b4 --- /dev/null +++ b/src/styles/App.css @@ -0,0 +1,21 @@ +#root { + width: 100%; + min-height: 100vh; + padding: 2rem; +} + +.mainContainer { + max-width: 1400px; + margin: 0 auto; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .weatherMain { + grid-template-columns: 1fr; + } + + .forecastGrid { + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + } +} diff --git a/src/styles/CurrentWeather.module.css b/src/styles/CurrentWeather.module.css new file mode 100644 index 0000000..e5ae80a --- /dev/null +++ b/src/styles/CurrentWeather.module.css @@ -0,0 +1,29 @@ +.weatherCard { + background: rgba(255, 255, 255, 0.9); + border-radius: 12px; + padding: 2rem; + margin: 2rem auto; + max-width: 800px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + } + + .weatherHeader { + display: flex; + justify-content: space-between; + align-items: centre; + margin-bottom: 1.5rem; + } + + .weatherMain { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 2rem; + margin-bottom: 2rem; + } + + .weatherDetails { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1rem; + } + \ No newline at end of file diff --git a/src/styles/FavoritesManager.module.css b/src/styles/FavoritesManager.module.css new file mode 100644 index 0000000..706c81b --- /dev/null +++ b/src/styles/FavoritesManager.module.css @@ -0,0 +1,40 @@ +.favoritesPanel { + background: rgba(255, 255, 255, 0.9); + border-radius: 8px; + padding: 1rem; + margin-top: 2rem; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.weatherSummary { + width: 100%; + cursor: pointer; + transition: background-color 0.2s; + padding: 0.5rem; +} + +.weatherSummary:hover { + background-color: rgba(0, 0, 0, 0.05); +} + +.cityHeader { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.5rem; +} + +.weatherDetails { + display: flex; + justify-content: space-between; + align-items: center; +} + +.favoriteItem { + padding: 0.5rem 0; +} + +.favoritesList { + max-height: 400px; + overflow-y: auto; +} diff --git a/src/styles/Forecast.module.css b/src/styles/Forecast.module.css new file mode 100644 index 0000000..c6156fd --- /dev/null +++ b/src/styles/Forecast.module.css @@ -0,0 +1,20 @@ +.forecastContainer { + margin: 2rem auto; + max-width: 1200px; + } + + .forecastGrid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 1.5rem; + padding: 1rem; + } + + .forecastItem { + background: rgba(255, 255, 255, 0.9); + border-radius: 8px; + padding: 1rem; + text-align: centre; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + \ No newline at end of file diff --git a/src/styles/SearchBar.module.css b/src/styles/SearchBar.module.css new file mode 100644 index 0000000..8efee64 --- /dev/null +++ b/src/styles/SearchBar.module.css @@ -0,0 +1,26 @@ +.searchContainer { + margin: 2rem auto; + max-width: 600px; + display: flex; + gap: 1rem; + } + + .searchInput { + flex-grow: 1; + padding: 0.8rem; + border-radius: 8px; + border: 1px solid #ccc; + } + + .searchButton { + background-color: #1a1a1a; + color: white; + padding: 0.8rem 1.5rem; + border-radius: 8px; + transition: background-colour 0.25s; + } + + .searchButton:hover { + background-color: #535bf2; + } + \ No newline at end of file diff --git a/src/styles/WeatherPage.module.css b/src/styles/WeatherPage.module.css new file mode 100644 index 0000000..c885d48 --- /dev/null +++ b/src/styles/WeatherPage.module.css @@ -0,0 +1,114 @@ +/* Main container styling */ +.weatherPageContainer { + max-width: 1200px; + margin: 0 auto; + padding: 2rem; + min-height: 100vh; + background: linear-gradient(to bottom right, #f0f4f8, #e9ecef); + } + + /* Search bar section */ + .searchSection { + margin-bottom: 3rem; + text-align: centre; + } + + /* Current weather display */ + .currentWeatherCard { + background: rgba(255, 255, 255, 0.9); + border-radius: 12px; + padding: 2rem; + margin-bottom: 2rem; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(5px); + } + + .weatherHeader { + display: flex; + justify-content: space-between; + align-items: centre; + margin-bottom: 1.5rem; + } + + .weatherMain { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 2rem; + margin-bottom: 2rem; + } + + .weatherDetails { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 1.5rem; + margin-top: 2rem; + } + + /* Favourites section */ + .favouritesSection { + background: rgba(255, 255, 255, 0.9); + border-radius: 12px; + padding: 1.5rem; + margin-top: 2rem; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + } + + /* Error and loading states */ + .errorMessage { + color: #dc3545; + background: rgba(220, 53, 69, 0.1); + padding: 1rem; + border-radius: 8px; + margin: 2rem 0; + text-align: centre; + } + + .loadingState { + display: flex; + justify-content: centre; + align-items: centre; + padding: 2rem; + font-size: 1.2rem; + colour: #6c757d; + } + + .loadingSpinner { + animation: spin 1s linear infinite; + margin-right: 0.5rem; + } + + @keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } + } + + /* Responsive design */ + @media (max-width: 768px) { + .weatherPageContainer { + padding: 1rem; + } + + .weatherMain { + grid-template-columns: 1fr; + } + + .weatherDetails { + grid-template-columns: 1fr; + } + } + + @media (min-width: 1024px) { + .mainContent { + display: grid; + grid-template-columns: 2fr 1fr; + gap: 2rem; + } + + .favouritesSection { + margin-top: 0; + height: fit-content; + position: sticky; + top: 1rem; + } + } + \ No newline at end of file diff --git a/src/styles/index.css b/src/styles/index.css new file mode 100644 index 0000000..a35d62a --- /dev/null +++ b/src/styles/index.css @@ -0,0 +1,25 @@ +/* Existing global styles remain unchanged */ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; +} + +body { + margin: 0; + display: flex; + justify-content: centre; + align-items: centre; + min-height: 100vh; +} + +.center { + text-align: center; +} + +.color-primary { + color: #646cff; +}