e44e24ace2
Add profile (verify-to-reveal, KYC rules, contact-change OTP flows, notifications/consent tabs, devices) and support (5 channels, message center, ticket routing, live chat, help center) updates, the geo BFF route, login auth flow, and a self-contained module rebuild prompt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
125 lines
6.3 KiB
TypeScript
125 lines
6.3 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
|
|
// Lightweight backend-for-frontend over free, keyless geo services.
|
|
// GET /api/geo?pin=203201 → India Post: { ok, pin, city, district, state, localities }
|
|
// GET /api/geo?q=alpha → India Post locality search: { ok, results }
|
|
// GET /api/geo?addr=350 5th&country=us → OpenStreetMap/Nominatim house-number autosuggest:
|
|
// { ok, results: [{ label, line1, line2, city, state, postal }] }
|
|
// If an upstream service is unreachable we fall back to a small built-in dataset
|
|
// so the registration flow keeps working offline / in the demo.
|
|
|
|
type Locality = { city: string; district: string; state: string; localities: string[] };
|
|
|
|
const FALLBACK_PINS: Record<string, Locality> = {
|
|
"203201": { city: "Greater Noida", district: "Gautam Buddh Nagar", state: "Uttar Pradesh", localities: ["Dankaur", "Dhanouri Kalan", "Jaganpur Afjalpur"] },
|
|
"201301": { city: "Noida", district: "Gautam Buddh Nagar", state: "Uttar Pradesh", localities: ["Noida Sector 1", "Noida Sector 27", "Hoshiarpur"] },
|
|
"110001": { city: "New Delhi", district: "Central Delhi", state: "Delhi", localities: ["Connaught Place", "Janpath", "Sansad Marg"] },
|
|
"122001": { city: "Gurugram", district: "Gurugram", state: "Haryana", localities: ["Gurgaon", "Civil Lines", "Gurgaon Sector 14"] },
|
|
"226001": { city: "Lucknow", district: "Lucknow", state: "Uttar Pradesh", localities: ["Lucknow GPO", "Hazratganj", "Lalbagh"] },
|
|
"302001": { city: "Jaipur", district: "Jaipur", state: "Rajasthan", localities: ["Jaipur GPO", "Kishanpole Bazar", "Mi Road"] },
|
|
"456010": { city: "Ujjain", district: "Ujjain", state: "Madhya Pradesh", localities: ["Ujjain GPO", "Freeganj", "Mahakal"] },
|
|
};
|
|
|
|
type IndiaPostOffice = { Name: string; District: string; State: string; Pincode: string; BranchType?: string };
|
|
type IndiaPostResp = Array<{ Status: string; PostOffice: IndiaPostOffice[] | null }>;
|
|
|
|
async function fetchUpstream(url: string): Promise<IndiaPostResp> {
|
|
const res = await fetch(url, {
|
|
headers: { "User-Agent": "YEIDA-Portal/1.0 (address autofill)" },
|
|
// The PIN → locality mapping is effectively static; cache for a day.
|
|
next: { revalidate: 86400 },
|
|
});
|
|
if (!res.ok) throw new Error(`upstream ${res.status}`);
|
|
return (await res.json()) as IndiaPostResp;
|
|
}
|
|
|
|
const clean = (s: string) => s.replace(/\s+/g, " ").trim();
|
|
|
|
export async function GET(request: Request) {
|
|
const { searchParams } = new URL(request.url);
|
|
const pin = (searchParams.get("pin") || "").replace(/\D/g, "");
|
|
const q = clean(searchParams.get("q") || "");
|
|
const addr = clean(searchParams.get("addr") || "");
|
|
const country = (searchParams.get("country") || "").toLowerCase().replace(/[^a-z]/g, "");
|
|
|
|
// --- PIN lookup → city / district / state + locality options ---
|
|
if (pin) {
|
|
if (pin.length !== 6) {
|
|
return NextResponse.json({ ok: false, error: "PIN must be 6 digits" }, { status: 400 });
|
|
}
|
|
try {
|
|
const data = await fetchUpstream(`https://api.postalpincode.in/pincode/${pin}`);
|
|
const offices = data?.[0]?.Status === "Success" ? data[0].PostOffice ?? [] : [];
|
|
if (offices.length) {
|
|
const first = offices[0];
|
|
const localities = [...new Set(offices.map((o) => clean(o.Name)))];
|
|
return NextResponse.json({
|
|
ok: true, pin, city: first.District, district: first.District, state: first.State, localities,
|
|
});
|
|
}
|
|
} catch {
|
|
/* fall through to the built-in fallback below */
|
|
}
|
|
const fb = FALLBACK_PINS[pin];
|
|
if (fb) {
|
|
return NextResponse.json({ ok: true, pin, ...fb, source: "fallback" });
|
|
}
|
|
return NextResponse.json({ ok: false, error: "PIN not found" }, { status: 404 });
|
|
}
|
|
|
|
// --- Address / locality typeahead by name ---
|
|
if (q) {
|
|
if (q.length < 3) return NextResponse.json({ ok: true, results: [] });
|
|
try {
|
|
const data = await fetchUpstream(`https://api.postalpincode.in/postoffice/${encodeURIComponent(q)}`);
|
|
const offices = data?.[0]?.Status === "Success" ? data[0].PostOffice ?? [] : [];
|
|
const results = offices.slice(0, 8).map((o) => ({
|
|
label: `${clean(o.Name)} · ${o.District}, ${o.State} — ${o.Pincode}`,
|
|
line2: clean(o.Name), city: o.District, state: o.State, pin: o.Pincode,
|
|
}));
|
|
return NextResponse.json({ ok: true, results });
|
|
} catch {
|
|
const ql = q.toLowerCase();
|
|
const results = Object.entries(FALLBACK_PINS)
|
|
.filter(([, v]) => v.city.toLowerCase().includes(ql) || v.localities.some((l) => l.toLowerCase().includes(ql)))
|
|
.slice(0, 8)
|
|
.map(([p, v]) => ({ label: `${v.city} · ${v.district}, ${v.state} — ${p}`, line2: v.city, city: v.city, state: v.state, pin: p }));
|
|
return NextResponse.json({ ok: true, results, source: "fallback" });
|
|
}
|
|
}
|
|
|
|
// --- Worldwide house-number / street autosuggest (non-India) via OpenStreetMap ---
|
|
if (addr) {
|
|
if (addr.length < 3) return NextResponse.json({ ok: true, results: [] });
|
|
try {
|
|
const url = `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(addr)}` +
|
|
`${country ? `&countrycodes=${country}` : ""}&format=jsonv2&addressdetails=1&limit=6`;
|
|
const res = await fetch(url, {
|
|
headers: { "User-Agent": "YEIDA-Portal/1.0 (address autofill)" },
|
|
next: { revalidate: 3600 },
|
|
});
|
|
if (!res.ok) throw new Error(`upstream ${res.status}`);
|
|
type NomRow = { display_name: string; name?: string; address?: Record<string, string> };
|
|
const rows = (await res.json()) as NomRow[];
|
|
const results = rows.map((r) => {
|
|
const a = r.address || {};
|
|
const line1 = clean([a.house_number, a.road || a.pedestrian || a.name || r.name].filter(Boolean).join(" "));
|
|
const city = a.city || a.town || a.village || a.municipality || a.suburb || a.county || "";
|
|
return {
|
|
label: r.display_name,
|
|
line1,
|
|
line2: a.neighbourhood || a.suburb || a.quarter || "",
|
|
city,
|
|
state: a.state || a.region || "",
|
|
postal: a.postcode || "",
|
|
};
|
|
}).filter((r) => r.line1);
|
|
return NextResponse.json({ ok: true, results });
|
|
} catch {
|
|
return NextResponse.json({ ok: true, results: [], source: "fallback" });
|
|
}
|
|
}
|
|
|
|
return NextResponse.json({ ok: false, error: "Provide a ?pin=, ?q= or ?addr= parameter" }, { status: 400 });
|
|
}
|