133 lines
4.9 KiB
TypeScript
133 lines
4.9 KiB
TypeScript
import { JSX } from "react";
|
|
import RefreshButton from "./RefreshButton";
|
|
|
|
// 1. Define explicit TypeScript interfaces for the Open-Meteo response
|
|
interface OpenMeteoResponse {
|
|
current: {
|
|
time: string;
|
|
temperature_2m: number;
|
|
relative_humidity_2m: number;
|
|
wind_speed_10m: number;
|
|
weather_code: number;
|
|
};
|
|
current_units: {
|
|
temperature_2m: string;
|
|
relative_humidity_2m: string;
|
|
wind_speed_10m: string;
|
|
};
|
|
}
|
|
|
|
// Map WMO Weather Interpretation Codes to friendly labels & badge styles
|
|
// https://emojipedia.org/ copy and paste!
|
|
function getWeatherInfo(code: number): { label: string; badgeClass: string } {
|
|
if (code === 0) return { label: "Clear Sky ☀️", badgeClass: "badge-warning" };
|
|
if (code >= 1 && code <= 3)
|
|
return { label: "Partly Cloudy ⛅", badgeClass: "badge-info" };
|
|
if (code >= 45 && code <= 48)
|
|
return { label: "Foggy 🌫️", badgeClass: "badge-ghost" };
|
|
if (code >= 51 && code <= 67)
|
|
return { label: "Rain / Drizzle 🌧️", badgeClass: "badge-primary" };
|
|
if (code >= 71 && code <= 77)
|
|
return { label: "Snow ❄️", badgeClass: "badge-secondary" };
|
|
return { label: "Stormy ⛈️", badgeClass: "badge-error" };
|
|
}
|
|
|
|
// 2. Async Server Component - direct data fetching on the server
|
|
export default async function WeatherPage(): Promise<JSX.Element> {
|
|
// Fetching London forecast from Open-Meteo (No API key needed)
|
|
const res = await fetch(
|
|
"https://api.open-meteo.com/v1/forecast?latitude=51.5074&longitude=-0.1278¤t=temperature_2m,relative_humidity_2m,wind_speed_10m,weather_code",
|
|
{
|
|
// Revalidate every 10 minutes (ISR - Incremental Static Regeneration)
|
|
next: { revalidate: 600 }
|
|
// next: {
|
|
// tags: ["weather-data"] // <--- Label this request with a tag
|
|
// }
|
|
}
|
|
);
|
|
|
|
// -------------------------------------------------------------
|
|
// ⏳ SIMULATION DELAY
|
|
// Slows down the revalidation so the STALE window stays open.
|
|
// await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
// -------------------------------------------------------------
|
|
|
|
if (!res.ok) {
|
|
throw new Error("Failed to fetch weather data from Open-Meteo.");
|
|
}
|
|
|
|
const data: OpenMeteoResponse = await res.json();
|
|
const weather = getWeatherInfo(data.current.weather_code);
|
|
|
|
return (
|
|
<main className="min-h-screen bg-base-200 p-8 flex justify-center items-center">
|
|
{/* daisyUI Card Container */}
|
|
<div className="card w-96 bg-base-100 shadow-xl border border-base-300">
|
|
<div className="card-body">
|
|
{/* Header section with Location & Condition Badge */}
|
|
<div className="flex justify-between items-center">
|
|
<h2 className="card-title text-2xl font-bold">London Weather</h2>
|
|
<span
|
|
className={`badge ${weather.badgeClass} gap-2 p-3 font-semibold`}
|
|
>
|
|
{weather.label}
|
|
</span>
|
|
</div>
|
|
|
|
<p className="text-xs text-base-content/60">
|
|
Last updated: {new Date(data.current.time).toLocaleTimeString()}
|
|
</p>
|
|
|
|
<div className="divider my-2"></div>
|
|
|
|
{/* daisyUI Stats Grid */}
|
|
<div className="stats stats-vertical shadow bg-base-200">
|
|
{/* Temperature Stat */}
|
|
<div className="stat">
|
|
<div className="stat-title">Temperature</div>
|
|
<div className="stat-value text-primary">
|
|
{data.current.temperature_2m}
|
|
<span className="text-2xl font-normal ml-1">
|
|
{data.current_units.temperature_2m}
|
|
</span>
|
|
</div>
|
|
<div className="stat-desc">Real-time server reading</div>
|
|
</div>
|
|
|
|
{/* Humidity Stat */}
|
|
<div className="stat">
|
|
<div className="stat-title">Humidity</div>
|
|
<div className="stat-value text-secondary">
|
|
{data.current.relative_humidity_2m}
|
|
<span className="text-xl font-normal ml-1">
|
|
{data.current_units.relative_humidity_2m}
|
|
</span>
|
|
</div>
|
|
<div className="stat-desc">Relative humidity</div>
|
|
</div>
|
|
|
|
{/* Wind Speed Stat */}
|
|
<div className="stat">
|
|
<div className="stat-title">Wind Speed</div>
|
|
<div className="stat-value text-accent">
|
|
{data.current.wind_speed_10m}
|
|
<span className="text-xl font-normal ml-1">
|
|
{data.current_units.wind_speed_10m}
|
|
</span>
|
|
</div>
|
|
<div className="stat-desc">10m surface level</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="card-actions justify-end mt-4">
|
|
<button className="btn btn-sm btn-outline btn-accent">
|
|
Live Data (Server Fetched)
|
|
</button>
|
|
</div>
|
|
<RefreshButton />
|
|
</div>
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|