From 54882285b619e72e123119d892da59bee32f1387 Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Mon, 12 Oct 2026 13:51:29 +0700 Subject: [PATCH] feat(weather): 6-day forecast FORECAST_DAYS=6 in lib/weather.ts is the single source for the fetch length and the UI labels (spot card + map popup). New count-interpolated labels spotUi.forecastDays / mapUi.forecastDays (en/es; other locales fall back to English). Legend colours moved to lib/skateabilityLevels.ts to keep the react-refresh lint at zero. --- AGENTS.md | 6 +++++- src/components/SpotMap.tsx | 3 ++- src/components/WeatherLegend.tsx | 14 +------------- src/i18n/en.ts | 2 ++ src/i18n/es.ts | 2 ++ src/lib/skateabilityLevels.ts | 15 +++++++++++++++ src/lib/weather.ts | 7 +++++-- src/pages/SpotDetailPage.tsx | 3 ++- 8 files changed, 34 insertions(+), 18 deletions(-) create mode 100644 src/lib/skateabilityLevels.ts diff --git a/AGENTS.md b/AGENTS.md index 534cb35..9fcf477 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2411,7 +2411,11 @@ composed in `legacy/index.ts` and spread into `en`/`es` (same pattern as weather card, **sunrise/sunset** (`weather.ts` fetches Open-Meteo daily `sunrise,sunset`), and the score's reasons now render as **chips** instead of a bullet-joined run-on. Popup `text-gray-500` → `gray-300` for contrast. Units stay - **Celsius / km/h** (deliberate — no unit toggle). + **Celsius / km/h** (deliberate — no unit toggle). The forecast is now **6 days** + (`FORECAST_DAYS` in `lib/weather.ts` is the single source for the fetch and the + labels; the legend colours live in `lib/skateabilityLevels.ts`). Labels use + `spotUi.forecastDays` / `mapUi.forecastDays` with a `{{count}}` param (the other + locales fall back to English until translated). - `skaterLabelsUi` (21) — skater stance/style/level + sponsor tiers in `types.ts` - `notifyUi` (28) — OS notification titles/bodies (`OsNotificationBridge`) - `legacyMiscUi` (~160) — scattered component/page literals, image `alt`/`aria-label`, diff --git a/src/components/SpotMap.tsx b/src/components/SpotMap.tsx index 5bd1ce1..77d0527 100644 --- a/src/components/SpotMap.tsx +++ b/src/components/SpotMap.tsx @@ -12,6 +12,7 @@ import type { BtcMapPlace } from '@/hooks/useBtcMapPlaces'; import { isRecentlyVerified, isBoosted } from '@/hooks/useBtcMapPlaces'; import { sanitizeUrl } from '@/lib/sanitizeUrl'; import { createCachedTileLayer } from '@/lib/cachedTileLayer'; +import { FORECAST_DAYS } from '@/lib/weather'; import { useTranslation } from '@/contexts/language'; /** Translation function shape, so module-level popup builders can receive `t`. */ @@ -627,7 +628,7 @@ function createPopupContent( const forecastTitle = document.createElement('div'); forecastTitle.className = 'text-xs font-semibold mb-1 text-gray-700'; - forecastTitle.textContent = t('mapUi.threeDayForecast'); + forecastTitle.textContent = t('mapUi.forecastDays', { count: FORECAST_DAYS }); forecastDiv.appendChild(forecastTitle); const forecastGrid = document.createElement('div'); diff --git a/src/components/WeatherLegend.tsx b/src/components/WeatherLegend.tsx index dc9b6a8..ac537db 100644 --- a/src/components/WeatherLegend.tsx +++ b/src/components/WeatherLegend.tsx @@ -1,17 +1,5 @@ import { useTranslation } from '@/contexts/language'; - -/** - * The skateability colour scale, mirroring `calculateSkateability` in lib/weather. - * Kept here (not derived at runtime) so the legend and the scorer can't drift - * silently — if the thresholds change there, change them here too. - */ -export const SKATEABILITY_LEVELS: { key: string; color: string }[] = [ - { key: 'weatherUi.skateability.perfect', color: '#00e400' }, - { key: 'weatherUi.skateability.good', color: '#92d050' }, - { key: 'weatherUi.skateability.okay', color: '#ffff00' }, - { key: 'weatherUi.skateability.poor', color: '#ff7e00' }, - { key: 'weatherUi.skateability.bad', color: '#ff0000' }, -]; +import { SKATEABILITY_LEVELS } from '@/lib/skateabilityLevels'; /** * Small map overlay explaining the weather-layer marker colours. Rendered by diff --git a/src/i18n/en.ts b/src/i18n/en.ts index cec97ea..2688551 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -934,6 +934,7 @@ mapUi: { humidity: 'Humidity', airQuality: 'Air quality', threeDayForecast: '3-Day Forecast', + forecastDays: '{{count}}-Day Forecast', gearForSale: 'Gear for sale ({{count}})', viewItems: 'View Items →', viewAllItems: 'View All {{count}} Items →', @@ -1366,6 +1367,7 @@ spotUi: { tagsSuggestedDesc: 'Your tag suggestions have been submitted.', takePhoto: 'Take Photo', threeDayForecast: '3-Day Forecast', + forecastDays: '{{count}}-Day Forecast', tipTheMapper: 'Tip the Mapper', tipTheMapperDesc: 'Found this spot useful? Send a zap to thank the mapper!', titleRequired: 'Title required', diff --git a/src/i18n/es.ts b/src/i18n/es.ts index f84ebb1..7ba05f5 100644 --- a/src/i18n/es.ts +++ b/src/i18n/es.ts @@ -934,6 +934,7 @@ mapUi: { humidity: 'Humedad', airQuality: 'Calidad del aire', threeDayForecast: 'Previsión a 3 días', + forecastDays: 'Pronóstico de {{count}} días', gearForSale: 'Equipo en venta ({{count}})', viewItems: 'Ver artículos →', viewAllItems: 'Ver los {{count}} artículos →', @@ -1366,6 +1367,7 @@ spotUi: { tagsSuggestedDesc: 'Tus sugerencias de etiquetas se han enviado.', takePhoto: 'Hacer foto', threeDayForecast: 'Previsión a 3 días', + forecastDays: 'Pronóstico de {{count}} días', tipTheMapper: 'Invita al autor', tipTheMapperDesc: '¿Te ha servido este spot? ¡Envía un zap para agradecer al autor!', titleRequired: 'Título obligatorio', diff --git a/src/lib/skateabilityLevels.ts b/src/lib/skateabilityLevels.ts new file mode 100644 index 0000000..5c8de87 --- /dev/null +++ b/src/lib/skateabilityLevels.ts @@ -0,0 +1,15 @@ +/** + * The skateability colour scale, shown in the map's weather legend. + * + * Kept as a plain module (not exported from a component file — that trips the + * `react-refresh` lint rule) and mirrors the colours emitted by + * `calculateSkateability` in `lib/weather.ts`. If a threshold there changes, + * change it here too. + */ +export const SKATEABILITY_LEVELS: { key: string; color: string }[] = [ + { key: 'weatherUi.skateability.perfect', color: '#00e400' }, + { key: 'weatherUi.skateability.good', color: '#92d050' }, + { key: 'weatherUi.skateability.okay', color: '#ffff00' }, + { key: 'weatherUi.skateability.poor', color: '#ff7e00' }, + { key: 'weatherUi.skateability.bad', color: '#ff0000' }, +]; diff --git a/src/lib/weather.ts b/src/lib/weather.ts index f164e22..3a007c0 100644 --- a/src/lib/weather.ts +++ b/src/lib/weather.ts @@ -190,8 +190,11 @@ export async function fetchAirQuality(lat: number, lng: number): Promise { const params = new URLSearchParams({ @@ -200,7 +203,7 @@ export async function fetchWeatherForecast(lat: number, lng: number): Promise 0 && (
-
{t('spotUi.threeDayForecast')}
+
{t('spotUi.forecastDays', { count: FORECAST_DAYS })}
{weatherData.forecast.daily.map((day, idx) => { const date = new Date(day.date);