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>
This commit is contained in:
Timo Uttenweiler
2026-03-17 11:21:11 +01:00
parent 5b84001c1e
commit facf8c9f69
59 changed files with 5800 additions and 233 deletions

View File

@@ -0,0 +1,61 @@
"use client";
import { useState, KeyboardEvent } from "react";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";
interface RoleChipsInputProps {
value: string[];
onChange: (roles: string[]) => void;
label?: string;
}
export function RoleChipsInput({ value, onChange, label = "Target Roles" }: RoleChipsInputProps) {
const [input, setInput] = useState("");
const add = (role: string) => {
const trimmed = role.trim();
if (trimmed && !value.includes(trimmed)) {
onChange([...value, trimmed]);
}
setInput("");
};
const remove = (role: string) => onChange(value.filter(r => r !== role));
const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" || e.key === ",") {
e.preventDefault();
add(input);
} else if (e.key === "Backspace" && !input && value.length > 0) {
remove(value[value.length - 1]);
}
};
return (
<div>
{label && <label className="block text-sm font-medium text-gray-300 mb-2">{label}</label>}
<div className="min-h-[44px] flex flex-wrap gap-2 p-2.5 bg-[#0d0d18] border border-[#2e2e3e] rounded-lg focus-within:border-blue-500 transition-colors">
{value.map(role => (
<span
key={role}
className="flex items-center gap-1 bg-blue-500/20 text-blue-300 border border-blue-500/30 rounded-md px-2.5 py-0.5 text-sm"
>
{role}
<button onClick={() => remove(role)} className="hover:text-white transition-colors">
<X className="w-3 h-3" />
</button>
</span>
))}
<input
className="flex-1 min-w-[120px] bg-transparent text-sm text-white outline-none placeholder:text-gray-600"
placeholder="Add role, press Enter..."
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={onKeyDown}
onBlur={() => input && add(input)}
/>
</div>
</div>
);
}