5-step guided wizard: find business → confirm → email → payment → success. Includes Google Places search API, Stripe checkout API, i18n (EN/ES), and CTA links updated to point to the new wizard. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
150 lines
3.9 KiB
TypeScript
150 lines
3.9 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import type { NextRequest } from "next/server";
|
|
|
|
interface Business {
|
|
place_id: string;
|
|
name: string;
|
|
address: string;
|
|
rating: number;
|
|
review_count: number;
|
|
category: string;
|
|
maps_url: string;
|
|
}
|
|
|
|
const GOOGLE_MAPS_URL_PATTERN =
|
|
/(?:google\.com\/maps|maps\.google\.com|goo\.gl\/maps|maps\.app\.goo\.gl)/i;
|
|
|
|
function isGoogleMapsUrl(input: string): boolean {
|
|
return GOOGLE_MAPS_URL_PATTERN.test(input);
|
|
}
|
|
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const body = await request.json();
|
|
const { query } = body as { query: string };
|
|
|
|
if (!query || typeof query !== "string" || query.trim().length < 2) {
|
|
return NextResponse.json(
|
|
{ error: "Query must be at least 2 characters" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
const trimmed = query.trim();
|
|
|
|
if (isGoogleMapsUrl(trimmed)) {
|
|
return handleUrlSearch(trimmed);
|
|
}
|
|
|
|
return handleTextSearch(trimmed);
|
|
} catch {
|
|
return NextResponse.json(
|
|
{ error: "Invalid request body" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
}
|
|
|
|
async function handleUrlSearch(url: string): Promise<NextResponse> {
|
|
const engineUrl = process.env.NEXT_PUBLIC_ENGINE_URL;
|
|
|
|
if (!engineUrl) {
|
|
return NextResponse.json(
|
|
{ error: "Engine service not configured" },
|
|
{ status: 503 },
|
|
);
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${engineUrl}/api/check-reviews`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ url }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
return NextResponse.json(
|
|
{ error: "Could not find business from URL" },
|
|
{ status: 404 },
|
|
);
|
|
}
|
|
|
|
const data = (await response.json()) as Record<string, unknown>;
|
|
|
|
const business: Business = {
|
|
place_id: (data.place_id as string) || "",
|
|
name: (data.name as string) || "Unknown Business",
|
|
address: (data.address as string) || "",
|
|
rating: (data.rating as number) || 0,
|
|
review_count: (data.review_count as number) || (data.reviews_count as number) || 0,
|
|
category: (data.category as string) || (data.type as string) || "",
|
|
maps_url: url,
|
|
};
|
|
|
|
return NextResponse.json({ businesses: [business] });
|
|
} catch {
|
|
return NextResponse.json(
|
|
{ error: "Failed to fetch business details" },
|
|
{ status: 502 },
|
|
);
|
|
}
|
|
}
|
|
|
|
async function handleTextSearch(query: string): Promise<NextResponse> {
|
|
const apiKey = process.env.GOOGLE_PLACES_API_KEY;
|
|
|
|
if (!apiKey) {
|
|
return NextResponse.json(
|
|
{ error: "Places search not configured" },
|
|
{ status: 503 },
|
|
);
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(
|
|
"https://places.googleapis.com/v1/places:searchText",
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-Goog-Api-Key": apiKey,
|
|
"X-Goog-FieldMask":
|
|
"places.id,places.displayName,places.formattedAddress,places.rating,places.userRatingCount,places.primaryTypeDisplayName,places.googleMapsUri",
|
|
},
|
|
body: JSON.stringify({
|
|
textQuery: query,
|
|
languageCode: "en",
|
|
}),
|
|
},
|
|
);
|
|
|
|
if (!response.ok) {
|
|
return NextResponse.json(
|
|
{ error: "Places search failed" },
|
|
{ status: 502 },
|
|
);
|
|
}
|
|
|
|
const data = (await response.json()) as Record<string, unknown>;
|
|
|
|
const businesses: Business[] = ((data.places as any[]) || [])
|
|
.slice(0, 5)
|
|
.map((place: any) => ({
|
|
place_id: place.id || "",
|
|
name: place.displayName?.text || "Unknown",
|
|
address: place.formattedAddress || "",
|
|
rating: place.rating || 0,
|
|
review_count: place.userRatingCount || 0,
|
|
category: place.primaryTypeDisplayName?.text || "",
|
|
maps_url: place.googleMapsUri || "",
|
|
}));
|
|
|
|
return NextResponse.json({ businesses });
|
|
} catch {
|
|
return NextResponse.json(
|
|
{ error: "Failed to search businesses" },
|
|
{ status: 502 },
|
|
);
|
|
}
|
|
}
|