Commit Graph

48 Commits

Author SHA1 Message Date
Timo Uttenweiler
b01d14b784 Fix internal API calls in Docker (use localhost instead of origin)
req.nextUrl.origin resolves to external domain behind a reverse proxy,
breaking server-to-server fetch calls. Use localhost:PORT instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 11:22:57 +02:00
Timo Uttenweiler
807b82f633 Fix Docker volume permissions for /data (SQLite)
Add su-exec to runner stage, run entrypoint as root to chown /data,
then drop to nextjs user for migrations and app start. Fixes permission
denied errors when Docker volume is mounted as root.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 11:19:27 +02:00
Timo Uttenweiler
7db914084e Stitch redesign, Energieversorger-Kampagne, UI improvements
- Apply Stitch design system to leadspeicher, suche, TopBar, globals.css
- Add Energieversorger queue campaign (Netzbetreiber, Fernwärme, Industriepark)
  with BW + Bayern priority, tracks usage per term+location combo
- Remove TopBar right-side actions (Leads finden, bell, settings)
- Remove mode tabs from manual search, rename KI button
- Fix Google Fonts @import order (move to <link> in layout.tsx)
- Add cursor-pointer globally via globals.css
- Responsive fixes for campaign buttons and KI button
- Fix .dockerignore to exclude .env from image build
- Add stadtwerke-cities API + city data (50 cities per Bundesland)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 10:08:00 +02:00
Timo Uttenweiler
54e0d22f9c mein-solar: full feature set
- Schema: companyType, topics, salesScore, salesReason, offerPackage, approved, approvedAt, SearchHistory table
- /api/search-history: GET (by mode) + POST (save query)
- /api/ai-search: stadtwerke/industrie/custom prompts with history dedup
- /api/enrich-leads: website scraping + GPT-4o-mini enrichment (fire-and-forget after each job)
- /api/generate-email: personalized outreach via GPT-4o
- Suche page: 3 mode tabs (Stadtwerke/Industrie/Freie Suche), Alle-Bundesländer queue button, AiSearchModal gets searchMode + history
- Leadspeicher: Bewertung dots column, Paket badge column, Freigeben toggle button, email generator in SidePanel, approved-only export option
- Leads API: approvedOnly + companyType filters, new fields in PATCH

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 21:06:07 +02:00
Timo Uttenweiler
e5172cbdc5 Remove count from AI search prompt, add query prioritization
- count no longer part of prompt or JSON output — fixed at 50 in backend
- Added prioritization rules: most common term wins (Dachdecker > Spengler)
- Cleaner examples without count field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 14:07:11 +02:00
Timo Uttenweiler
edbf8cb1e2 Improve AI search system prompt
- Explains Google Maps pipeline context so model understands query constraints
- Adds splitting strategy for large regions and multi-industry searches
- 4 concrete JSON examples covering common use cases
- count derived from user context, no hardcoded default in prompt
- Strict output format instructions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 14:03:19 +02:00
Timo Uttenweiler
0f5d18dac7 Add KI-Suche via OpenRouter GPT-4o-mini
- /api/ai-search: sends user description to GPT-4o-mini, returns 2-4
  structured query/region pairs as JSON
- AiSearchModal: textarea, generates previews, user selects queries to run
- KI-Suche button in hero section of /suche page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 14:00:54 +02:00
TimoUttenweiler
a39a98b6dc feat: start SERP supplement in parallel as soon as Maps scraping stabilizes
When Maps scraping finishes (totalLeads stable for one poll, < targetCount),
fire the SERP supplement job immediately — don't wait for Anymailfinder
enrichment to complete. Both jobs now poll independently; results are merged
and deduplicated by domain once both are done.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 11:28:41 +02:00
TimoUttenweiler
1719062b47 feat: Hover-Effekt für Nur-neue-speichern Button
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 11:01:33 +02:00
TimoUttenweiler
f94f7c6314 feat: Nur neue speichern als Button in den Suchergebnissen
Button erscheint in der Ergebnis-Headerzeile sobald vorhandene Leads
dabei sind. Löscht diese aus dem Leadspeicher und zeigt Bilanz-Toast.
Verschwindet nach Ausführung. Checkbox vor der Suche entfernt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 11:00:10 +02:00
TimoUttenweiler
6264ebf786 feat: Alle/Nur-neue Tabs + Option Nur neue Leads speichern
- Suchergebnis-Filter als Tabs: [Alle (46)] [Nur neue (X)] — beide klickbar
- Checkbox "Nur neue Leads speichern" erscheint vor der Suche
- Bei aktivierter Option: nach Abschluss werden vorhandene Leads
  automatisch aus dem Leadspeicher gelöscht, Toast zeigt Bilanz

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 10:56:30 +02:00
TimoUttenweiler
25234b70ee fix: Leads ohne Domain werden gefiltert und nicht gespeichert
Maps-Ergebnisse ohne Domain werden vor Speicherung herausgefiltert.
sinkLeadsToVault überspringt Leads ohne Domain komplett.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 10:54:30 +02:00
TimoUttenweiler
929d5ab3a1 fix: Leads ohne E-Mails wenn Anymailfinder-Guthaben leer
Anymailfinder-Fehler (z.B. 402) markiert Job nicht mehr als failed.
Maps- und SERP-Jobs schließen als complete ab und liefern die gefundenen
Unternehmen ohne Kontaktdaten — SERP-Supplement triggert danach normal.

- maps-enrich: Anymailfinder in eigenem try-catch, Fehler → complete
- serp-enrich: SERP-Rohdaten zuerst speichern, dann Enrichment versuchen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 10:52:50 +02:00
TimoUttenweiler
89a176700d feat: GPT-4.1 optimierte Ergänzungssuche bei Maps-Lücke
Wenn Google Maps weniger Leads findet als angefragt, wird automatisch
eine optimierte Suchanfrage via GPT-4.1 generiert und als SERP-Job
gestartet, um die Lücke zu füllen. Die KI-Query wird im LoadingCard
angezeigt. Fallback auf Original-Query wenn kein OpenAI-Key konfiguriert.

- lib/services/openai.ts: GPT-4.1 Query-Generator
- app/api/search/supplement: neuer Endpoint (GPT + SERP-Job)
- LoadingCard: ruft /api/search/supplement statt direkt SERP
- apiKey.ts + .env.local.example: openai Key-Support

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 10:43:33 +02:00
TimoUttenweiler
aa6707b8bc feat: Kundensuche – Progressbar, SERP-Supplement, Dedup, Löschen, Neu-Filter
- Progressbar geht nie mehr rückwärts (Math.min-Cap entfernt)
- E-Mails-suchen-Phase wird immer kurz angezeigt bevor Fertig
- SERP-Supplement startet automatisch wenn Maps < Zielanzahl liefert
- Suchergebnisse bleiben nach Abschluss sichtbar (kein Redirect)
- Dedup in leadVault strikt nach Domain (verhindert Duplikate)
- isNew-Flag pro Result (Batch-Query gegen bestehende Vault-Domains)
- Nur-neue-Filter + vorhanden-Badge in Suchergebnissen
- Einzeln und Bulk löschen aus Suchergebnissen + Leadspeicher
- Fehlermeldung zeigt echten API-Fehler (z.B. 402 Anymailfinder)
- SERP-Supplement aus /api/search entfernt (LoadingCard übernimmt)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 10:25:43 +02:00
Timo Uttenweiler
c232f0cb79 Fix stats: count kontaktiert via tag instead of status field 2026-03-27 17:15:09 +01:00
Timo Uttenweiler
11197c9db1 Progress bar: forward-only crawl, never goes backward 2026-03-27 17:13:30 +01:00
Timo Uttenweiler
4c82e96f5a Remove 200 leads option from search 2026-03-27 17:11:03 +01:00
Timo Uttenweiler
a1d2c34f36 Search: Maps primary + SERP supplement for count > 60
- Always use Google Maps (max 60 per call)
- If count > 60: fire SERP job in background for additional results
- Dedup handled automatically by LeadVault domain upsert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 17:10:09 +01:00
Timo Uttenweiler
bf3fcd4210 Increase suche page padding and max-width 2026-03-27 17:06:04 +01:00
Timo Uttenweiler
8c00d4ea6f Increase leadspeicher padding to 72px 2026-03-27 17:05:34 +01:00
Timo Uttenweiler
b2a963a901 Improve SearchCard: hover effects, responsive grid, smooth transitions 2026-03-27 17:04:23 +01:00
Timo Uttenweiler
6b7ac42d1d Style suche hero like admin maps header 2026-03-27 17:03:12 +01:00
Timo Uttenweiler
9939ba9fdb Update leadspeicher subtitle text 2026-03-27 17:02:17 +01:00
Timo Uttenweiler
ea3138ac64 Remove Quelle column from customer leadspeicher 2026-03-27 17:01:56 +01:00
Timo Uttenweiler
a67dd2cc8c Add padding to leadspeicher page 2026-03-27 17:01:07 +01:00
Timo Uttenweiler
df90477bef Use admin leadspeicher UI in customer branch, fix Topbar import
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 16:59:59 +01:00
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
TimoUttenweiler
47b78fa749 feat: Rebranding von LeadFlow zu OnyvaLeads
Alle UI-Labels, Dateinamen, API-Bezeichner und package.json auf OnyvaLeads umgestellt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v1.0-admin
2026-03-25 15:10:46 +01:00
TimoUttenweiler
a9a0be157f fix: Turso/libsql HTTP adapter für Windows-Kompatibilität
- PrismaLibSql Factory statt pre-created client (URL_INVALID fix)
- libsql:// URL zu https:// konvertieren für @libsql/client/http
- prisma.config.ts nutzt TURSO_DATABASE_URL wenn gesetzt
- .env.local.example mit Turso-Docs erweitert
- @libsql/client auf 0.17.0 pinned

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 12:00:19 +01:00
Timo Uttenweiler
621267594c Remove Google Maps from social filter label (keep in logic)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 20:53:37 +01:00
Timo Uttenweiler
115cdacd08 UI improvements: Leadspeicher, Maps enrichment, exports
- Rename LeadVault → Leadspeicher throughout (sidebar, topbar, page)
- SidePanel: full lead detail view with contact, source, tags (read-only), Google Maps link for address
- Tags: kontaktiert stored as tag (toggleable), favorit tag toggle
- Remove Status column, StatusBadge dropdown, Priority feature
- Remove Aktualisieren button from Leadspeicher
- Bulk actions: remove status dropdown
- Export: LeadVault Excel-only, clean columns, freeze row + autofilter
- Export dropdown: click-based (fix overflow-hidden clipping)
- ExportButtons: remove CSV, Excel only everywhere
- Maps page: post-search Anymailfinder enrichment button
- ProgressCard: "Suche läuft..." instead of "Warte auf Anymailfinder-Server..."
- Quick SERP renamed to "Schnell neue Suche"
- Results page: Excel export, always-enabled download button
- Anymailfinder: fix bulk field names, array-of-arrays format
- Apify: fix countryCode lowercase
- API: sourceTerm search, contacted/favorite tag filters

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 18:12:31 +01:00
Timo Uttenweiler
f914ab6e47 fix: Maps-Vault-Sync sofort nach Google Maps, nicht erst nach Anymailfinder
- Google Maps Ergebnisse werden direkt nach der Suche in LeadVault gespeichert
- Anymailfinder-Fehler führen nicht mehr zum Datenverlust
- Bessere Fehlermeldungen: echter Fehlertext statt hardcoded Message
- Anymailfinder-Fehlermeldung auf Deutsch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 18:01:44 +01:00
Timo Uttenweiler
82c4244233 feat: Suchbegriff-Spalte + Filter-Chips im LeadVault
- GET /api/leads/search-terms: distinct Suchbegriffe aus DB
- Filter-Bar: Suchbegriff-Chips (amber), klickbar zum Filtern
- Tabelle: Suchbegriff-Spalte mit Chip — Klick filtert direkt
- Mehrere Suchbegriffe gleichzeitig filterbar (OR-Logik)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 17:53:40 +01:00
Timo Uttenweiler
fa177a982f feat: Adresse & Telefon in LeadVault Tabelle + Side Panel
- Lead-Modell: address Feld hinzugefügt (Migration)
- Maps-Sync: address + phone aus source JSON extrahiert
- LeadVault Tabelle: Telefon/Adresse als kombinierte Spalte
- LeadVault Side Panel: Adresse mit Pin-Icon
- Telefonnummer ist klickbar (tel: Link)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 17:49:29 +01:00
Timo Uttenweiler
8dc135a8f7 fix: Indeterminate Progress-Anzeige bei Anymailfinder Bulk-Warten
- Wenn current=0 und total>0: Shimmer-Animation statt leerer Balken
- Bouncing Dots + "Warte auf Anymailfinder-Server..." Text
- Hinweis "dauert ca. 1–3 Min. für X Domains"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 17:44:53 +01:00
Timo Uttenweiler
8fb881cbbc fix: Export-Encoding und Excel-Support
- CSV: UTF-8 BOM + \r\n Zeilenenden → Umlaute in Excel korrekt
- CSV: deutsche Spaltennamen, Tags als kommagetrennte Liste
- Excel (.xlsx): nativer Export via xlsx-Library mit Spaltenbreiten
- Export-Dropdown: CSV und Excel jeweils für aktuelle Ansicht und nur mit E-Mail

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 17:38:49 +01:00
Timo Uttenweiler
042fbeb672 feat: LeadVault - zentrale Lead-Datenbank mit CRM-Funktionen
- Prisma-Schema: Lead + LeadEvent Modelle (Migration 20260320)
- lib/services/leadVault.ts: sinkLeadsToVault mit Deduplizierung
- Auto-Sync: alle 4 Pipelines schreiben Leads in LeadVault
- GET /api/leads: Filter, Sortierung, Pagination (Server-side)
- PATCH/DELETE /api/leads/[id]: Status, Priorität, Tags, Notizen
- POST /api/leads/bulk: Bulk-Aktionen für mehrere Leads
- GET /api/leads/stats: Statistiken + 7-Tage-Sparkline
- POST /api/leads/quick-serp: SERP-Capture ohne Enrichment
- GET /api/leads/export: CSV-Export mit allen Feldern
- app/leadvault/page.tsx: vollständige UI mit Stats, Quick SERP,
  Filter-Leiste, sortierbare Tabelle, Bulk-Aktionen, Side Panel
- Sidebar: LeadVault-Eintrag mit Live-Badge (neue Leads)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 17:33:12 +01:00
Timo Uttenweiler
6711633a5d feat: Mehrere Suchbegriffe im Maps-Tab (Chip-System)
- Keywords als Chip-Input statt einzelnem Textfeld
- Queries = jedes Keyword × jede Region (Kreuzprodukt)
- Vorschläge für gängige deutsche B2B-Branchen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 14:12:47 +01:00
Timo Uttenweiler
ea93d674a2 feat: API Keys via Umgebungsvariablen konfigurierbar
- Neuer getApiKey() Helper: prüft zuerst ENV-Vars, dann DB
- Alle Job-Routes nutzen getApiKey() statt direktem DB-Lookup
- Credentials-Status berücksichtigt ENV-Vars (Sidebar-Haken)
- .env.local.example: Platzhalter für alle 4 API Keys

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 13:58:41 +01:00
Timo Uttenweiler
39760b20a9 refactor: Entscheider-Kategorie als Einzelauswahl (Radio)
- Nur noch eine Kategorie gleichzeitig wählbar (Array → einzelner Wert)
- ceo-Label: "CEO / Owner / President / Founder" mit Empfohlen-Badge
- API-Aufruf sendet [category] statt categories[]
- Überflüssige Validierungen entfernt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 13:05:14 +01:00
Timo Uttenweiler
717bc4f943 refactor: Entscheider-Kategorien auf Führungsebene fokussiert
Entfernt: HR, IT, Einkauf, Logistik
Behalten: CEO/Inhaber/Gründer, COO, CTO, CMO, CFO, Vertriebsleiter
LinkedIn-Guide: Zielpositionen aktualisiert (Founder, Co-Founder, CEO, CTO, COO, CMO, Owner, Principal, Partner, CXO, Geschäftsführer, Inhaber)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 12:52:33 +01:00
Timo Uttenweiler
f6bdc65b1e feat: übersetze gesamte UI auf Deutsch
- Alle Seiten (AirScale, LinkedIn, SERP, Ergebnisse, Einstellungen) auf Deutsch
- Gemeinsame Komponenten übersetzt: StatusBadge, ResultsTable-Spalten, FileDropZone, ExportButtons
- Sidebar API-Status-Label und TopBar-Breadcrumbs auf Deutsch
- Alle Toast-Nachrichten und Fehlermeldungen auf Deutsch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 12:40:05 +01:00
Timo Uttenweiler
7486517827 feat: add Google Maps → Email pipeline (Tab 4)
- New Maps page with keyword + region chips, German city presets, query preview, enrichment toggle
- Google Maps Places API (New) service with pagination and deduplication
- maps-enrich job route: Maps search → store raw leads → optional Anymailfinder bulk enrichment
- Settings: Google Maps API key credential card with Places API instructions
- Sidebar: MapPin nav item + googlemaps credential status indicator
- Results: maps job type with MapPin icon (text-green-400)
- Credentials API: added googlemaps to SERVICES array and test endpoint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 11:40:08 +01:00
Timo Uttenweiler
aa50f46748 docs: add Coolify deployment instructions to README
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 11:21:42 +01:00
Timo Uttenweiler
facf8c9f69 Initial commit: LeadFlow lead generation platform
Full-stack Next.js 16 app with three scraping pipelines:
- AirScale CSV → Anymailfinder Bulk Decision Maker search
- LinkedIn Sales Navigator → Vayne → Anymailfinder email enrichment
- Apify Google SERP → domain extraction → Anymailfinder bulk enrichment

Includes Docker multi-stage build + docker-compose for Coolify deployment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 11:21:11 +01:00
Timo
5b84001c1e feat: initial commit 2026-03-17 10:51:44 +01:00
Timo
410602f8c5 Initial commit from Create Next App 2026-03-17 10:50:51 +01:00