init
This commit is contained in:
@@ -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: [] };
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user