This commit is contained in:
2026-09-28 23:15:47 +05:30
commit 605c6a2e58
41 changed files with 4299 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
"use server";
import { headers } from "next/headers";
import { allowRequest, clientIp } from "../../lib/rate-limit";
import { lookupBikes } from "../../lib/status";
import type { StatusResult } from "../../lib/workshop";
export async function lookupStatus(_prev: StatusResult | null, formData: FormData): Promise<StatusResult> {
const headerList = await headers();
const ip = clientIp(headerList.get("x-forwarded-for"));
if (!allowRequest(ip)) {
return {
message: null,
error: "Too many lookups. Wait a minute and try again.",
bikes: [],
};
}
try {
return await lookupBikes(String(formData.get("phone") ?? ""));
} catch {
console.error("status lookup failed");
return { message: null, error: "Lookup failed. Try again.", bikes: [] };
}
}
+12
View File
@@ -0,0 +1,12 @@
import { StatusForm } from "./status-form";
export const dynamic = "force-dynamic";
export default function StatusPage() {
return (
<main className="status">
<h1>100kmph</h1>
<StatusForm />
</main>
);
}
+53
View File
@@ -0,0 +1,53 @@
"use client";
import { useActionState } from "react";
import { lookupStatus } from "./actions";
import { NOT_IN_WORKSHOP, type StatusResult } from "../../lib/workshop";
function formatWhen(iso: string | null): string {
if (!iso) return "";
const date = new Date(iso);
if (Number.isNaN(date.getTime())) return "";
return date.toLocaleString("en-LK", { timeZone: "Asia/Colombo" });
}
export function StatusForm() {
const [state, action, pending] = useActionState<StatusResult | null, FormData>(lookupStatus, null);
return (
<form action={action}>
<label>
Phone
<input name="phone" inputMode="tel" autoComplete="tel" required />
</label>
<button type="submit" disabled={pending}>
Check
</button>
{state?.error ? <p className="error">{state.error}</p> : null}
{state?.message ? <p>{state.message}</p> : null}
{state && state.bikes.length > 0 ? (
<ul>
{state.bikes.map((bike, index) => {
const when = formatWhen(bike.stageAt);
return (
<li key={`${bike.registration}-${index}`}>
<div>{bike.registration}</div>
<div>
{bike.make} {bike.model}
</div>
{bike.inWorkshop ? (
<div>
{bike.stageLabel}
{when ? ` · ${when}` : ""}
</div>
) : (
<div>{NOT_IN_WORKSHOP}</div>
)}
</li>
);
})}
</ul>
) : null}
</form>
);
}