import { useQuery } from "@tanstack/react-query" import { Fragment, useEffect, useState } from "react" import cn from "./components/lib/cn" import { StatusSeverity, getLineColor, getStatusBorderColor, tflDisruptionsQuery } from "./tfl" import { DEFAULT_LATITUDE, DEFAULT_LONGITUDE, currentWeatherQuery, dailyForecastQuery, getWeatherIcon, weatherDescriptionQuery, } from "./weather" function App() { return (
) } function Tile({ decorations = true, children, className, }: { decorations?: boolean; children: React.ReactNode; className?: string }) { return (
{decorations && ( <>
)} {children}
) } function DateTimeTile() { const [time, setTime] = useState(new Date()) const formattedDate = time.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric", }) const formattedTime = time.toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit", }) useEffect(() => { const interval = setInterval(() => { setTime(new Date()) }, 1000) return () => clearInterval(interval) }, []) return (

{formattedDate}

{formattedTime}

) } function WeatherTile() { const { data: currentWeatherData, isLoading: isLoadingCurrentWeather, error: errorCurrentWeather, } = useQuery({ ...currentWeatherQuery(DEFAULT_LATITUDE, DEFAULT_LONGITUDE), refetchInterval: 5 * 60 * 1000, // 5 minutes refetchIntervalInBackground: true, }) const { data: dailyForecastData, isLoading: isLoadingDailyForecast, error: errorDailyForecast, } = useQuery({ ...dailyForecastQuery(DEFAULT_LATITUDE, DEFAULT_LONGITUDE), refetchInterval: 5 * 60 * 1000, // 5 minutes refetchIntervalInBackground: true, }) const { data: weatherDescriptionData, isLoading: isLoadingWeatherDescription, error: errorWeatherDescription, } = useQuery({ ...weatherDescriptionQuery(DEFAULT_LATITUDE, DEFAULT_LONGITUDE), refetchInterval: 60 * 60 * 1000, // 1 hour refetchIntervalInBackground: true, }) const isLoading = isLoadingCurrentWeather || isLoadingDailyForecast const error = errorCurrentWeather || errorDailyForecast if (isLoading) { return (

Loading weather

) } if (error || !currentWeatherData?.currentWeather) { return (

Error loading weather

{error?.message ?? "Unknown error"}

) } const currentWeather = currentWeatherData.currentWeather const temperature = Math.round(currentWeather.temperature) const lowTemp = Math.round(dailyForecastData?.forecastDaily?.days[0].temperatureMin ?? 0) const highTemp = Math.round(dailyForecastData?.forecastDaily?.days[0].temperatureMax ?? 0) const percentage = lowTemp && highTemp ? (temperature - lowTemp) / (highTemp - lowTemp) : 0 const highlightIndexStart = Math.floor((1 - percentage) * 23) const WeatherIcon = getWeatherIcon(currentWeather.conditionCode) let weatherDescriptionContent: string if (isLoadingWeatherDescription) { weatherDescriptionContent = "Loading weather description" } else if (errorWeatherDescription) { weatherDescriptionContent = `Error: ${errorWeatherDescription.message}` } else if (!weatherDescriptionData?.description) { weatherDescriptionContent = "No weather description available" } else { weatherDescriptionContent = weatherDescriptionData.description } return (

H:{highTemp}°

L:{lowTemp}°

{Array.from({ length: 24 }).map((_, index) => { if (index === highlightIndexStart) { return (
key={index} className={cn("w-10 bg-teal-400 h-[2px]")} />

{temperature}°

) } return (
key={index} className={cn( "w-4", index >= highlightIndexStart ? "bg-teal-400 w-8 h-[2px]" : "bg-neutral-400 w-4 h-[1px]", )} /> ) })}

{weatherDescriptionContent}

) } function TFLTile() { const { data: tflData, isLoading: isLoadingTFL, error: errorTFL, } = useQuery({ ...tflDisruptionsQuery(), select: (data) => { data.disruptions.sort((a, b) => { if (a.lineName.match(/northern/i)) return -1 return a.statusSeverity - b.statusSeverity }) return data }, refetchInterval: 5 * 60 * 1000, // 5 minutes refetchIntervalInBackground: true, }) if (isLoadingTFL) { return (

Loading TfL

) } return ( {tflData?.goodService.includes("Northern") && ( )} {tflData?.disruptions.map((disruption) => (
))}
) } function TFLDistruptionItem({ lineId, lineName, reason, severity, }: { lineId: string; lineName: string; reason: string; severity: number }) { return ( <>

{lineName}

{reason}

) } export default App