Files
lead-scraper/app/suche/page.tsx
Timo Uttenweiler 60073b97c9 feat: OnyvaLeads customer UI — Suche + Leadspeicher
- New Topbar: logo, 2-tab pill switcher, live "Neu" badge
- /suche page: SearchCard, LoadingCard, ExamplePills
- /leadspeicher page: full leads table with filters, pagination
- StatusBadge, StatusPopover, LeadSidePanel, BulkActionBar
- POST /api/search: unified search entry point → serp-enrich
- Remove Sidebar + old TopBar from layout
- Title: OnyvaLeads, redirect / → /suche

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 16:48:05 +01:00

101 lines
2.9 KiB
TypeScript

"use client";
import { useState, useCallback } from "react";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { SearchCard } from "@/components/search/SearchCard";
import { LoadingCard } from "@/components/search/LoadingCard";
export default function SuchePage() {
const router = useRouter();
const [query, setQuery] = useState("");
const [region, setRegion] = useState("");
const [count, setCount] = useState(50);
const [loading, setLoading] = useState(false);
const [jobId, setJobId] = useState<string | null>(null);
function handleChange(field: "query" | "region" | "count", value: string | number) {
if (field === "query") setQuery(value as string);
if (field === "region") setRegion(value as string);
if (field === "count") setCount(value as number);
}
async function handleSubmit() {
if (!query.trim() || loading) return;
setLoading(true);
setJobId(null);
try {
const res = await fetch("/api/search", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: query.trim(), region: region.trim(), count }),
});
if (!res.ok) {
const err = await res.json() as { error?: string };
throw new Error(err.error || "Fehler beim Starten der Suche");
}
const data = await res.json() as { jobId: string };
setJobId(data.jobId);
} catch (err) {
const msg = err instanceof Error ? err.message : "Unbekannter Fehler";
toast.error(msg);
setLoading(false);
}
}
const handleDone = useCallback((total: number) => {
setLoading(false);
toast.success(`${total} Leads gefunden — Leadspeicher wird geöffnet`, {
duration: 3000,
});
setTimeout(() => {
router.push("/leadspeicher");
}, 1500);
}, [router]);
const handleError = useCallback(() => {
setLoading(false);
setJobId(null);
toast.error("Suche fehlgeschlagen. Bitte prüfe deine API-Einstellungen.");
}, []);
return (
<div
style={{
padding: "40px 40px",
maxWidth: 680,
margin: "0 auto",
}}
>
{/* Hero */}
<div style={{ textAlign: "center", marginBottom: 32 }}>
<h2 style={{ fontSize: 22, fontWeight: 500, color: "#ffffff", margin: 0, marginBottom: 8 }}>
Leads finden
</h2>
<p style={{ fontSize: 13, color: "#9ca3af", margin: 0 }}>
Suchbegriff eingeben wir finden passende Unternehmen mit Kontaktdaten.
</p>
</div>
{/* Search Card */}
<SearchCard
query={query}
region={region}
count={count}
loading={loading}
onChange={handleChange}
onSubmit={handleSubmit}
/>
{/* Loading Card */}
{loading && jobId && (
<LoadingCard
jobId={jobId}
onDone={handleDone}
onError={handleError}
/>
)}
</div>
);
}