first commit

This commit is contained in:
Jeannette Chin committed 2026-10-04 12:04:52 +01:00
commit 2d54228143
28 files changed
+8067

No files matched your search

+133
View File
@@ -0,0 +1,133 @@
import { JSX } from "react";
// --- TYPES ---
interface TfLStatusReason {
statusSeverityDescription: string;
reason?: string;
}
interface TfLLineStatus {
id: string;
name: string;
lineStatuses: TfLStatusReason[];
}
interface OpenMeteoWeather {
current: {
temperature_2m: number;
weather_code: number;
};
current_units: {
temperature_2m: string;
};
}
// --- FETCH FUNCTIONS ---
async function getTfLTransitStatus(): Promise<TfLLineStatus[]> {
// Public endpoint - No API key needed
const res = await fetch(
"https://api.tfl.gov.uk/line/mode/tube,elizabeth-line,dlr/status",
{
next: { revalidate: 300 }, // Cache for 5 minutes
},
);
if (!res.ok) throw new Error("Failed to fetch TfL line statuses");
return res.json();
}
async function getLondonWeather(): Promise<OpenMeteoWeather> {
// Public endpoint - No API key needed
const res = await fetch(
"https://api.open-meteo.com/v1/forecast?latitude=51.5074&longitude=-0.1278&current=temperature_2m,weather_code",
{ next: { revalidate: 300 } },
);
if (!res.ok) throw new Error("Failed to fetch London weather");
return res.json();
}
// Helper: Badge color depending on TfL status
function getStatusBadge(statusText: string): string {
if (statusText === "Good Service") return "badge-success";
if (statusText.includes("Minor") || statusText.includes("Part"))
return "badge-warning";
return "badge-error";
}
// --- SERVER COMPONENT ---
export default async function TrafficAndWeatherPage(): Promise<JSX.Element> {
// PARALLEL FETCHING: Triggers both external network calls simultaneously
const [transitData, weatherData] = await Promise.all([
getTfLTransitStatus(),
getLondonWeather(),
]);
return (
<main className="min-h-screen bg-base-200 p-8">
<div className="max-w-5xl mx-auto space-y-8">
{/* Header */}
<header className="flex justify-between items-end border-b border-base-300 pb-4">
<div>
<h1 className="text-3xl font-bold">London Transit & Weather</h1>
<p className="text-sm text-base-content/60">
Fetched in parallel on the server via{" "}
<code className="badge badge-outline font-mono">
Promise.all()
</code>
</p>
</div>
{/* Quick Weather Stat Card */}
<div className="stat bg-base-100 shadow rounded-box w-auto py-2 px-4">
<div className="stat-title text-xs">London Temp</div>
<div className="stat-value text-xl text-primary">
{weatherData.current.temperature_2m}
{weatherData.current_units.temperature_2m}
</div>
</div>
</header>
{/* TfL Tube & Rail Status Grid */}
<section className="space-y-4">
<h2 className="text-xl font-semibold flex items-center gap-2">
<span>🚇</span> Real-Time Transit Status (TfL Live)
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{transitData.map((line) => {
const currentStatus = line.lineStatuses[0];
const statusDesc =
currentStatus?.statusSeverityDescription || "Unknown";
const disruptionDetail = currentStatus?.reason;
return (
<div
key={line.id}
className="card bg-base-100 shadow-sm border border-base-300"
>
<div className="card-body p-4">
<div className="flex justify-between items-center">
<h3 className="font-bold text-base">{line.name}</h3>
<span
className={`badge ${getStatusBadge(statusDesc)} text-xs font-semibold`}
>
{statusDesc}
</span>
</div>
{disruptionDetail && (
<p className="text-xs text-base-content/70 mt-2 line-clamp-2">
{disruptionDetail}
</p>
)}
</div>
</div>
);
})}
</div>
</section>
</div>
</main>
);
}