import { site } from "@/config/site"; import type { PlanVisitData } from "@/lib/schema"; export interface LocateResponse { ok: boolean; approximate?: { lat: number; lng: number; city?: string; region?: string; confidence: "low" | "medium" }; reason?: string; } export interface GeocodeResponse { ok: boolean; location?: { lat: number; lng: number; city?: string; state?: string }; error?: string; } export type IntegrationStatus = "queued" | "sent" | "skipped" | "failed" | "mock"; export interface SubmitResponse { ok: boolean; submissionId?: string; duplicate?: boolean; email?: { status: IntegrationStatus }; sms?: { status: IntegrationStatus }; error?: string; fieldErrors?: Record; retryAfter?: number; } const DEFAULT_TIMEOUT = 10_000; export class ApiError extends Error { status: number; constructor(message: string, status: number) { super(message); this.status = status; } } async function request(path: string, init: RequestInit & { timeoutMs?: number } = {}): Promise { const controller = new AbortController(); const t = setTimeout(() => controller.abort(), init.timeoutMs ?? DEFAULT_TIMEOUT); try { const res = await fetch(`${site.apiBase}${path}`, { ...init, signal: controller.signal, headers: { Accept: "application/json", "X-Requested-With": "visit-page", ...(init.headers ?? {}) }, credentials: "same-origin", }); const text = await res.text(); let data: unknown = null; try { data = text ? JSON.parse(text) : null; } catch { throw new ApiError(`Bad response (${res.status})`, res.status); } if (!res.ok && (!data || typeof data !== "object")) throw new ApiError(`Request failed (${res.status})`, res.status); return data as T; } finally { clearTimeout(t); } } /** Approximate, server-side IP geolocation. Never blocks the page. */ export function locateApprox(): Promise { return request("/locate.php", { method: "GET", timeoutMs: 4000 }); } export function geocodeZip(zip: string): Promise { return request(`/geocode.php?zip=${encodeURIComponent(zip)}`, { method: "GET", timeoutMs: 8000 }); } export function submitPlanVisit(data: PlanVisitData): Promise { return request("/plan-visit.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), timeoutMs: 20_000, }); }