first commit
This commit is contained in:
commit
2d54228143
28 files changed
+8067
No files matched your search
@@ -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¤t=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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user