import React, { useState, useEffect, useRef } from 'react';
import {
Camera,
ScanLine,
Users,
Home,
Settings,
Search,
Plus,
Check,
X,
ChevronRight,
Briefcase,
DollarSign,
Globe,
Zap,
ArrowUpRight,
Database,
Smartphone,
Mic,
Save,
Loader2,
Linkedin,
Mail,
Phone,
AlertCircle
} from 'lucide-react';
/**
* Mock Data Generators
*/
const COMPANIES = ['TechFlow', 'Solaris AI', 'NexGen Data', 'Orbit Inc.', 'Vantage Point', 'BlueSky Logistics'];
const ROLES = ['VP of Sales', 'CTO', 'Marketing Director', 'Product Lead', 'Senior Engineer', 'Founder'];
const FIRST_NAMES = ['Sarah', 'James', 'Elena', 'Marcus', 'Priya', 'David', 'Alex', 'Jordan'];
const LAST_NAMES = ['Chen', 'Miller', 'Rodriguez', 'Okoro', 'Smith', 'Wu', 'Johnson', 'Silva'];
const STACK_OPTIONS = ['Salesforce, AWS, React', 'HubSpot, Azure, Angular', 'Custom CRM, GCP, Vue', 'Zoho, DigitalOcean, Svelte'];
const REVENUE_OPTIONS = ['$5M - $10M', '$10M - $50M', '$1M - $5M', '$50M+', 'Pre-Revenue'];
const FUNDING_OPTIONS = ['Series A', 'Bootstrapped', 'Series B', 'Seed Round', 'Public'];
const generateId = () => Math.random().toString(36).substr(2, 9);
const getRandomItem = (arr) => arr[Math.floor(Math.random() * arr.length)];
// Initial seed data
const INITIAL_LEADS = Array.from({ length: 5 }).map((_, i) => ({
id: generateId(),
firstName: getRandomItem(FIRST_NAMES),
lastName: getRandomItem(LAST_NAMES),
title: getRandomItem(ROLES),
company: getRandomItem(COMPANIES),
email: `contact@${getRandomItem(COMPANIES).toLowerCase().replace(/\\s/g, '')}.com`,
phone: '+1 (555) 012-3456',
avatarColor: ['bg-blue-500', 'bg-indigo-500', 'bg-green-500', 'bg-purple-500'][i % 4],
score: Math.floor(Math.random() * 40) + 60, // Score 60-100
status: i % 2 === 0 ? 'Synced' : 'Pending',
capturedAt: new Date(Date.now() - Math.floor(Math.random() * 1000000000)).toISOString(),
notes: "Met at the networking mixer. Interested in Q3 deployment.",
enrichment: {
revenue: getRandomItem(REVENUE_OPTIONS),
funding: getRandomItem(FUNDING_OPTIONS),
stack: getRandomItem(STACK_OPTIONS),
employees: Math.floor(Math.random() * 200) + 10,
linkedIn: true
}
}));
// Components
const Button = ({ children, onClick, variant = 'primary', className = '', disabled = false, icon: Icon }) => {
const baseStyle = "flex items-center justify-center px-4 py-3 rounded-xl font-semibold transition-all active:scale-95 touch-manipulation";
const variants = {
primary: "bg-blue-600 text-white shadow-lg shadow-blue-200 hover:bg-blue-700",
secondary: "bg-slate-100 text-slate-700 hover:bg-slate-200",
outline: "border-2 border-slate-200 text-slate-600 hover:border-slate-300",
ghost: "bg-transparent text-slate-500 hover:bg-slate-50",
danger: "bg-red-50 text-red-600 hover:bg-red-100"
};
return (
<button
onClick={onClick}
disabled={disabled}
className={`${baseStyle} ${variants[variant]} ${className} ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}
>
{Icon && <Icon size={18} className="mr-2" />}
{children}
</button>
);
};
const MetricCard = ({ label, value, trend, icon: Icon, color }) => (
<div className="bg-white p-4 rounded-2xl shadow-sm border border-slate-100 flex items-start space-x-3">
<div className={`p-3 rounded-xl ${color} bg-opacity-10`}>
<Icon size={20} className={color.replace('bg-', 'text-')} />
</div>
<div>
<p className="text-xs font-medium text-slate-500 uppercase tracking-wide">{label}</p>
<h3 className="text-xl font-bold text-slate-900 mt-1">{value}</h3>
{trend && <p className="text-xs text-green-600 font-medium mt-1 flex items-center">
<ArrowUpRight size={12} className="mr-0.5" /> {trend}
</p>}
</div>
</div>
);
const ScoreBadge = ({ score }) => {
let color = "bg-red-100 text-red-700";
if (score >= 80) color = "bg-green-100 text-green-700";
else if (score >= 50) color = "bg-yellow-100 text-yellow-700";
return (
<div className={`flex items-center space-x-1 px-2 py-1 rounded-full text-xs font-bold ${color}`}>
<Zap size={10} fill="currentColor" />
<span>{score}</span>
</div>
);
};
/**
* Scanner Component (Extracted for better Hook management)
*/
const ScannerOverlay = ({ onClose, onSave }) => {
const [phase, setPhase] = useState('camera'); // camera, processing, form
const [scanMode, setScanMode] = useState('card'); // card, qr, manual
const [scannedData, setScannedData] = useState(null);
const [cameraError, setCameraError] = useState(false);
const videoRef = useRef(null);
const streamRef = useRef(null);
useEffect(() => {
let active = true;
const startCamera = async () => {
if (scanMode === 'manual' || phase !== 'camera') {
if (streamRef.current) {
streamRef.current.getTracks().forEach(track => track.stop());
streamRef.current = null;
}
return;
}
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'environment' }
});
if (active && videoRef.current) {
videoRef.current.srcObject = stream;
streamRef.current = stream;
setCameraError(false);
} else {
// Cleanup if component unmounted while loading
stream.getTracks().forEach(track => track.stop());
}
} catch (err) {
console.error("Camera access denied or not available", err);
if (active) setCameraError(true);
}
};
startCamera();
return () => {
active = false;
if (streamRef.current) {
streamRef.current.getTracks().forEach(track => track.stop());
}
};
}, [scanMode, phase]);
const takePhoto = () => {
// Ideally we would capture the frame here using canvas,
// but for the prototype we will just simulate the data extraction
// from the "captured" image.
setPhase('processing');
// Simulate AI Processing
setTimeout(() => {
setScannedData({
firstName: getRandomItem(FIRST_NAMES),
lastName: getRandomItem(LAST_NAMES),
company: getRandomItem(COMPANIES),
title: getRandomItem(ROLES),
email: 'scanning...',
phone: 'scanning...',
});
setPhase('form');
}, 2000);
};
const finalizeLead = (e) => {
e.preventDefault();
const newLead = {
id: generateId(),
...scannedData,
email: `${scannedData.firstName.toLowerCase()}@${scannedData.company.toLowerCase().replace(/\\s/g, '')}.com`,
phone: '+1 (415) 555-0199',
avatarColor: 'bg-indigo-600',
score: Math.floor(Math.random() * 30) + 70,
status: 'Pending',
capturedAt: new Date().toISOString(),
notes: "Captured via Scan at TechConference 2024",
enrichment: {
revenue: getRandomItem(REVENUE_OPTIONS),
funding: getRandomItem(FUNDING_OPTIONS),
stack: getRandomItem(STACK_OPTIONS),
employees: Math.floor(Math.random() * 500),
linkedIn: true
}
};
onSave(newLead);
};
if (phase === 'camera') {
return (
<div className="fixed inset-0 bg-black z-50 flex flex-col">
<div className="flex justify-between items-center p-6 text-white pt-8 bg-black/50 z-20 absolute top-0 w-full">
<button onClick={onClose}><X size={28} /></button>
<span className="font-semibold">Capture Lead</span>
<button><Settings size={24} /></button>
</div>
<div className="flex-1 relative bg-black overflow-hidden flex flex-col items-center justify-center">
{/* Real Camera Viewfinder */}
{scanMode !== 'manual' && (
<div className="relative w-full h-full">
{cameraError ? (
<div className="absolute inset-0 flex flex-col items-center justify-center text-white p-6 text-center">
<AlertCircle size={48} className="text-red-500 mb-4" />
<h3 className="text-xl font-bold mb-2">Camera Access Denied</h3>
<p className="text-slate-400">Please allow camera access or use manual entry.</p>
<button onClick={() => setScanMode('manual')} className="mt-6 text-blue-400 font-bold">Switch to Manual</button>
</div>
) : (
<video
ref={videoRef}
autoPlay
playsInline
muted
className="absolute inset-0 w-full h-full object-cover"
/>
)}
{/* Scanning Overlay */}
<div className="absolute inset-0 flex items-center justify-center pointer-events-none z-10">
<div className="w-72 h-48 border-2 border-blue-400 rounded-2xl flex flex-col justify-between p-4 bg-white/5 backdrop-blur-[2px]">
<div className="flex justify-between">
<div className="w-4 h-4 border-t-4 border-l-4 border-blue-500 rounded-tl-lg"></div>
<div className="w-4 h-4 border-t-4 border-r-4 border-blue-500 rounded-tr-lg"></div>
</div>
<div className="text-center text-white/80 text-sm font-medium animate-pulse drop-shadow-md">
Align {scanMode === 'card' ? 'Business Card' : 'Badge QR'}
</div>
<div className="flex justify-between">
<div className="w-4 h-4 border-b-4 border-l-4 border-blue-500 rounded-bl-lg"></div>
<div className="w-4 h-4 border-b-4 border-r-4 border-blue-500 rounded-br-lg"></div>
</div>
</div>
</div>
</div>
)}
{scanMode === 'manual' && (
<div className="w-full h-full bg-white p-6 rounded-t-3xl mt-24 z-20 animate-slide-up">
<h3 className="text-xl font-bold text-slate-800 mb-6">Manual Entry</h3>
<form onSubmit={(e) => {
e.preventDefault();
setScannedData({
firstName: 'Manual',
lastName: 'Entry',
company: 'Entered Inc',
title: 'Director'
});
setPhase('processing');
setTimeout(() => setPhase('form'), 1500);
}} className="space-y-4">
<input type="text" placeholder="Full Name" className="w-full p-4 bg-slate-50 rounded-xl border-none focus:ring-2 focus:ring-blue-500" required />
<input type="text" placeholder="Company" className="w-full p-4 bg-slate-50 rounded-xl border-none focus:ring-2 focus:ring-blue-500" required />
<input type="email" placeholder="Email" className="w-full p-4 bg-slate-50 rounded-xl border-none focus:ring-2 focus:ring-blue-500" />
<Button className="w-full mt-4" variant="primary">Enrich & Save</Button>
</form>
</div>
)}
</div>
{/* Camera Controls */}
{scanMode !== 'manual' && (
<div className="bg-black/80 backdrop-blur-xl p-8 pb-12 z-20 absolute bottom-0 w-full">
<div className="flex justify-center space-x-8 mb-8">
{['card', 'qr', 'manual'].map((m) => (
<button
key={m}
onClick={() => setScanMode(m)}
className={`text-sm font-semibold uppercase tracking-wider ${scanMode === m ? 'text-blue-400' : 'text-slate-500'}`}
>
{m === 'card' ? 'Biz Card' : m}
</button>
))}
</div>
<div className="flex items-center justify-between px-8">
<div className="w-12 h-12 bg-slate-800 rounded-full flex items-center justify-center">
<Zap size={20} className="text-white" />
</div>
<button
onClick={takePhoto}
disabled={cameraError}
className={`w-20 h-20 rounded-full border-4 border-white flex items-center justify-center relative group ${cameraError ? 'opacity-50' : ''}`}
>
<div className="w-16 h-16 bg-white rounded-full group-active:scale-90 transition-transform"></div>
</button>
<div className="w-12 h-12"></div>
</div>
</div>
)}
</div>
);
}
if (phase === 'processing') {
return (
<div className="fixed inset-0 bg-blue-600 z-50 flex flex-col items-center justify-center text-white p-8 text-center">
<div className="relative mb-8">
<div className="absolute inset-0 bg-blue-400 rounded-full animate-ping opacity-25"></div>
<div className="bg-white p-4 rounded-full relative z-10">
<Loader2 size={40} className="text-blue-600 animate-spin" />
</div>
</div>
<h2 className="text-2xl font-bold mb-2">Analyzing Image...</h2>
<p className="text-blue-100 mb-8">Extracting contact info & enriching with company data.</p>
<div className="w-full max-w-xs space-y-3 text-left bg-blue-700/50 p-6 rounded-xl backdrop-blur">
<div className="flex items-center space-x-3 text-blue-100">
<Check size={16} /> <span>OCR Text Extraction</span>
</div>
<div className="flex items-center space-x-3 text-blue-100">
<Check size={16} /> <span>Validating Email</span>
</div>
<div className="flex items-center space-x-3 text-blue-200 animate-pulse">
<Loader2 size={16} className="animate-spin" /> <span>Fetching Company Revenue</span>
</div>
</div>
</div>
);
}
if (phase === 'form') {
return (
<div className="fixed inset-0 bg-slate-50 z-50 overflow-y-auto">
<div className="sticky top-0 bg-white border-b border-slate-100 p-4 flex justify-between items-center z-10 pt-8">
<button onClick={() => { setPhase('camera'); }} className="text-slate-500">Cancel</button>
<h2 className="font-bold text-lg">Review Lead</h2>
<button onClick={finalizeLead} className="text-blue-600 font-bold">Save</button>
</div>
<div className="p-6 space-y-6">
<div className="bg-blue-50 border border-blue-100 p-4 rounded-xl flex items-start space-x-3">
<Zap className="text-blue-600 mt-1" size={20} />
<div>
<h4 className="font-bold text-blue-900 text-sm">Leadcap AI Intelligence</h4>
<p className="text-blue-700 text-xs mt-1">We found extra data for {scannedData.company}. Score: High Potential.</p>
</div>
</div>
<div className="space-y-4">
<div>
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">Full Name</label>
<input
type="text"
value={`${scannedData.firstName} ${scannedData.lastName}`}
onChange={(e) => {
const [first, ...last] = e.target.value.split(' ');
setScannedData({...scannedData, firstName: first, lastName: last.join(' ')})
}}
className="w-full p-3 bg-white border border-slate-200 rounded-xl"
/>
</div>
<div>
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">Company</label>
<input
type="text"
value={scannedData.company}
onChange={(e) => setScannedData({...scannedData, company: e.target.value})}
className="w-full p-3 bg-white border border-slate-200 rounded-xl"
/>
</div>
<div>
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">Title</label>
<input
type="text"
value={scannedData.title}
onChange={(e) => setScannedData({...scannedData, title: e.target.value})}
className="w-full p-3 bg-white border border-slate-200 rounded-xl"
/>
</div>
</div>
<div className="pt-4 pb-24">
<Button onClick={finalizeLead} className="w-full shadow-xl shadow-blue-200" variant="primary">
Confirm & Add to Pipeline
</Button>
</div>
</div>
</div>
);
}
};
/**
* Main App Component
*/
export default function LeadcapApp() {
const [activeTab, setActiveTab] = useState('home');
const [leads, setLeads] = useState(INITIAL_LEADS);
const [selectedLead, setSelectedLead] = useState(null);
const [isScanning, setIsScanning] = useState(false);
const [showToast, setShowToast] = useState(null);
const [crmConnected, setCrmConnected] = useState(true);
// Stats derived from leads
const totalLeads = leads.length;
const pendingSync = leads.filter(l => l.status === 'Pending').length;
const highPriority = leads.filter(l => l.score > 80).length;
const triggerToast = (msg, type = 'success') => {
setShowToast({ msg, type });
setTimeout(() => setShowToast(null), 3000);
};
const handleSyncLead = (id) => {
if (!crmConnected) {
triggerToast("Connect CRM in settings first!", "error");
return;
}
setLeads(leads.map(l => l.id === id ? { ...l, status: 'Synced' } : l));
triggerToast("Lead successfully pushed to Salesforce");
};
const handleCreateLead = (newLead) => {
setLeads([newLead, ...leads]);
setIsScanning(false);
setSelectedLead(newLead); // Open the new lead immediately
triggerToast("Lead captured & enriched!");
};
// Views
const renderHome = () => (
<div className="pb-24 space-y-6 animate-fade-in">
<header className="flex justify-between items-center px-6 pt-8 pb-4 bg-white sticky top-0 z-10 border-b border-slate-100">
<div className="flex items-center space-x-2">
<div className="bg-blue-600 p-2 rounded-lg">
<ScanLine className="text-white" size={20} />
</div>
<span className="text-xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-700 to-indigo-600">
Leadcap.ai
</span>
</div>
<div className="relative">
<div className="absolute top-0 right-0 w-2.5 h-2.5 bg-red-500 rounded-full border-2 border-white"></div>
<button className="p-2 hover:bg-slate-50 rounded-full text-slate-600">
<Database size={24} />
</button>
</div>
</header>
<div className="px-6">
<h2 className="text-2xl font-bold text-slate-800 mb-4">Dashboard</h2>
<div className="grid grid-cols-2 gap-4">
<MetricCard
label="Total Leads"
value={totalLeads}
trend="+12%"
icon={Users}
color="bg-blue-500"
/>
<MetricCard
label="Pipeline Value"
value="$420k"
trend="+5%"
icon={DollarSign}
color="bg-green-500"
/>
<MetricCard
label="Pending Sync"
value={pendingSync}
icon={Database}
color="bg-orange-500"
/>
<MetricCard
label="High Priority"
value={highPriority}
icon={Zap}
color="bg-purple-500"
/>
</div>
</div>
<div className="px-6">
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-bold text-slate-800">Recent Activity</h3>
<button onClick={() => setActiveTab('leads')} className="text-blue-600 text-sm font-medium">View All</button>
</div>
<div className="space-y-3">
{leads.slice(0, 3).map(lead => (
<div key={lead.id} onClick={() => setSelectedLead(lead)} className="bg-white p-4 rounded-xl border border-slate-100 shadow-sm flex items-center justify-between active:bg-slate-50 transition-colors cursor-pointer">
<div className="flex items-center space-x-3">
<div className={`w-10 h-10 rounded-full ${lead.avatarColor} flex items-center justify-center text-white font-bold`}>
{lead.firstName[0]}{lead.lastName[0]}
</div>
<div>
<h4 className="font-semibold text-slate-800">{lead.firstName} {lead.lastName}</h4>
<p className="text-xs text-slate-500">{lead.company}</p>
</div>
</div>
<div className="flex flex-col items-end space-y-1">
<span className="text-xs text-slate-400">2m ago</span>
<ScoreBadge score={lead.score} />
</div>
</div>
))}
</div>
</div>
</div>
);
const renderLeadsList = () => (
<div className="flex flex-col h-full bg-slate-50 pb-20">
<div className="bg-white p-6 pt-12 border-b border-slate-100 sticky top-0 z-10">
<h2 className="text-2xl font-bold text-slate-800 mb-4">My Leads</h2>
<div className="relative">
<Search className="absolute left-3 top-3 text-slate-400" size={20} />
<input
type="text"
placeholder="Search name, company, or tag..."
className="w-full pl-10 pr-4 py-3 bg-slate-100 rounded-xl border-none focus:ring-2 focus:ring-blue-500 outline-none"
/>
</div>
</div>
<div className="flex-1 overflow-y-auto p-4 space-y-3">
{leads.map(lead => (
<div
key={lead.id}
onClick={() => setSelectedLead(lead)}
className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm hover:shadow-md transition-shadow cursor-pointer"
>
<div className="flex justify-between items-start mb-2">
<div className="flex items-center space-x-3">
<div className={`w-12 h-12 rounded-full ${lead.avatarColor} flex items-center justify-center text-white text-lg font-bold`}>
{lead.firstName[0]}{lead.lastName[0]}
</div>
<div>
<h3 className="font-bold text-slate-800">{lead.firstName} {lead.lastName}</h3>
<p className="text-sm text-slate-500">{lead.title} @ {lead.company}</p>
</div>
</div>
<ScoreBadge score={lead.score} />
</div>
<div className="flex items-center justify-between mt-4 pt-3 border-t border-slate-100">
<div className="flex space-x-2">
{lead.status === 'Synced' ? (
<span className="text-xs font-medium text-green-600 flex items-center bg-green-50 px-2 py-1 rounded">
<Check size={12} className="mr-1" /> Synced to CRM
</span>
) : (
<span className="text-xs font-medium text-orange-600 flex items-center bg-orange-50 px-2 py-1 rounded">
<Database size={12} className="mr-1" /> Not Synced
</span>
)}
</div>
<span className="text-xs text-slate-400">{new Date(lead.capturedAt).toLocaleDateString()}</span>
</div>
</div>
))}
{leads.length === 0 && (
<div className="text-center py-12 text-slate-400">
<Users size={48} className="mx-auto mb-4 opacity-50" />
<p>No leads yet. Start scanning!</p>
</div>
)}
</div>
</div>
);
const renderLeadDetail = () => {
if (!selectedLead) return null;
return (
<div className="fixed inset-0 bg-slate-50 z-40 overflow-y-auto animate-slide-up pb-20">
<div className="sticky top-0 bg-white/80 backdrop-blur-md border-b border-slate-200 p-4 flex justify-between items-center z-10 pt-8">
<button onClick={() => setSelectedLead(null)} className="p-2 hover:bg-slate-100 rounded-full">
<ChevronRight className="rotate-180 text-slate-600" size={24} />
</button>
<span className="font-semibold text-slate-800">Lead Profile</span>
<button className="p-2 hover:bg-slate-100 rounded-full text-slate-600">
<Settings size={24} />
</button>
</div>
<div className="p-6">
{/* Header Profile */}
<div className="flex flex-col items-center mb-8">
<div className={`w-24 h-24 rounded-full ${selectedLead.avatarColor} flex items-center justify-center text-white text-3xl font-bold mb-4 shadow-xl shadow-slate-200`}>
{selectedLead.firstName[0]}{selectedLead.lastName[0]}
</div>
<h2 className="text-2xl font-bold text-slate-900">{selectedLead.firstName} {selectedLead.lastName}</h2>
<p className="text-slate-500 font-medium">{selectedLead.title} @ {selectedLead.company}</p>
<div className="flex space-x-3 mt-4">
<button className="p-3 bg-white border border-slate-200 rounded-full text-blue-600 shadow-sm">
<Mail size={20} />
</button>
<button className="p-3 bg-white border border-slate-200 rounded-full text-blue-600 shadow-sm">
<Phone size={20} />
</button>
<button className="p-3 bg-white border border-slate-200 rounded-full text-blue-600 shadow-sm">
<Linkedin size={20} />
</button>
</div>
</div>
{/* AI Enrichment Card */}
<div className="bg-gradient-to-br from-indigo-600 to-blue-700 rounded-2xl p-6 text-white shadow-lg shadow-blue-200 mb-6 relative overflow-hidden">
<div className="absolute top-0 right-0 p-4 opacity-10">
<Zap size={120} />
</div>
<div className="relative z-10">
<div className="flex justify-between items-start mb-6">
<div>
<h3 className="font-bold text-lg flex items-center">
<Zap size={18} className="mr-2 text-yellow-300" fill="currentColor" />
Leadcap Intelligence
</h3>
<p className="text-blue-200 text-sm">Enriched {new Date().toLocaleDateString()}</p>
</div>
<div className="bg-white/20 backdrop-blur-md px-3 py-1 rounded-full text-sm font-bold border border-white/30">
Score: {selectedLead.score}/100
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="bg-white/10 p-3 rounded-lg backdrop-blur-sm">
<div className="text-blue-200 text-xs mb-1 uppercase tracking-wider">Revenue</div>
<div className="font-semibold">{selectedLead.enrichment.revenue}</div>
</div>
<div className="bg-white/10 p-3 rounded-lg backdrop-blur-sm">
<div className="text-blue-200 text-xs mb-1 uppercase tracking-wider">Funding</div>
<div className="font-semibold">{selectedLead.enrichment.funding}</div>
</div>
<div className="col-span-2 bg-white/10 p-3 rounded-lg backdrop-blur-sm">
<div className="text-blue-200 text-xs mb-1 uppercase tracking-wider">Tech Stack</div>
<div className="font-semibold text-sm">{selectedLead.enrichment.stack}</div>
</div>
</div>
</div>
</div>
{/* Notes Section */}
<div className="bg-white p-5 rounded-2xl border border-slate-100 shadow-sm mb-6">
<h3 className="font-bold text-slate-800 mb-3 flex items-center">
<Mic size={18} className="mr-2 text-slate-400" /> Notes
</h3>
<textarea
className="w-full bg-slate-50 rounded-xl p-3 text-sm text-slate-700 border-none focus:ring-2 focus:ring-blue-500"
rows="3"
defaultValue={selectedLead.notes}
/>
<div className="flex justify-end mt-2">
<button className="text-xs font-bold text-blue-600 uppercase tracking-wide">Save Note</button>
</div>
</div>
{/* Actions */}
<Button
onClick={() => handleSyncLead(selectedLead.id)}
variant={selectedLead.status === 'Synced' ? 'secondary' : 'primary'}
className="w-full mb-3"
disabled={selectedLead.status === 'Synced'}
>
{selectedLead.status === 'Synced' ? (
<>
<Check size={20} className="mr-2" /> Synced to Salesforce
</>
) : (
<>
<Database size={20} className="mr-2" /> Sync to Salesforce
</>
)}
</Button>
<div className="h-6"></div>
</div>
</div>
);
};
const renderSettings = () => (
<div className="h-full bg-slate-50 p-6 pt-12 animate-fade-in">
<h2 className="text-2xl font-bold text-slate-800 mb-8">Settings</h2>
<div className="space-y-6">
<div className="bg-white p-5 rounded-2xl shadow-sm border border-slate-100">
<h3 className="font-bold text-slate-700 mb-4">Integrations</h3>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center text-blue-600">
<Database size={20} />
</div>
<div>
<div className="font-semibold text-slate-800">Salesforce</div>
<div className="text-xs text-slate-500">{crmConnected ? 'Connected as @jdoe' : 'Not connected'}</div>
</div>
</div>
<div
onClick={() => {
setCrmConnected(!crmConnected);
triggerToast(crmConnected ? "Disconnected CRM" : "Salesforce Connected!", crmConnected ? "error" : "success");
}}
className={`w-12 h-7 rounded-full p-1 cursor-pointer transition-colors ${crmConnected ? 'bg-green-500' : 'bg-slate-300'}`}
>
<div className={`w-5 h-5 bg-white rounded-full shadow-md transform transition-transform ${crmConnected ? 'translate-x-5' : ''}`}></div>
</div>
</div>
<div className="flex items-center justify-between opacity-50">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 bg-orange-100 rounded-lg flex items-center justify-center text-orange-600">
<Globe size={20} />
</div>
<div>
<div className="font-semibold text-slate-800">HubSpot</div>
<div className="text-xs text-slate-500">Upgrade to Pro</div>
</div>
</div>
<div className="text-xs font-bold text-blue-600">UPGRADE</div>
</div>
</div>
<div className="bg-white p-5 rounded-2xl shadow-sm border border-slate-100">
<h3 className="font-bold text-slate-700 mb-4">Data Enrichment</h3>
<div className="space-y-3">
<div className="flex justify-between text-sm">
<span className="text-slate-600">Phone Verification</span>
<span className="text-green-600 font-bold">Active</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-slate-600">Company Revenue</span>
<span className="text-green-600 font-bold">Active</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-slate-600">Tech Stack Detection</span>
<span className="text-green-600 font-bold">Active</span>
</div>
</div>
</div>
</div>
<div className="mt-8 text-center">
<p className="text-xs text-slate-400">Leadcap.ai v1.0.4 Beta</p>
<button className="text-red-500 text-sm font-semibold mt-2">Log Out</button>
</div>
</div>
);
return (
<div className="w-full h-screen bg-slate-200 flex justify-center overflow-hidden font-sans text-slate-900">
{/* Mobile Container */}
<div className="w-full max-w-md h-full bg-slate-50 flex flex-col relative shadow-2xl overflow-hidden">
{/* Main Content Area */}
<div className="flex-1 overflow-y-auto no-scrollbar">
{activeTab === 'home' && renderHome()}
{activeTab === 'leads' && renderLeadsList()}
{activeTab === 'settings' && renderSettings()}
</div>
{/* Floating Scanned Lead Details Overlay */}
{selectedLead && renderLeadDetail()}
{/* Scanner Overlay (Takes over full screen) */}
{isScanning && (
<ScannerOverlay
onClose={() => setIsScanning(false)}
onSave={handleCreateLead}
/>
)}
{/* Bottom Navigation */}
<div className="bg-white border-t border-slate-200 px-6 py-2 pb-6 flex justify-between items-center relative z-20">
<button
onClick={() => setActiveTab('home')}
className={`flex flex-col items-center space-y-1 ${activeTab === 'home' ? 'text-blue-600' : 'text-slate-400'}`}
>
<Home size={24} strokeWidth={activeTab === 'home' ? 2.5 : 2} />
<span className="text-[10px] font-medium">Home</span>
</button>
<button
onClick={() => setActiveTab('leads')}
className={`flex flex-col items-center space-y-1 ${activeTab === 'leads' ? 'text-blue-600' : 'text-slate-400'}`}
>
<Users size={24} strokeWidth={activeTab === 'leads' ? 2.5 : 2} />
<span className="text-[10px] font-medium">Leads</span>
</button>
{/* Central Scan Button */}
<div className="relative -top-6">
<button
onClick={() => setIsScanning(true)}
className="w-16 h-16 bg-gradient-to-tr from-blue-600 to-indigo-600 rounded-full flex items-center justify-center text-white shadow-lg shadow-blue-200 transform transition-transform hover:scale-105 active:scale-95"
>
<Camera size={28} />
</button>
</div>
<button
onClick={() => {}}
className="flex flex-col items-center space-y-1 text-slate-300 cursor-not-allowed"
>
<Briefcase size={24} strokeWidth={2} />
<span className="text-[10px] font-medium">Pipeline</span>
</button>
<button
onClick={() => setActiveTab('settings')}
className={`flex flex-col items-center space-y-1 ${activeTab === 'settings' ? 'text-blue-600' : 'text-slate-400'}`}
>
<Settings size={24} strokeWidth={activeTab === 'settings' ? 2.5 : 2} />
<span className="text-[10px] font-medium">Settings</span>
</button>
</div>
{/* Toast Notification */}
{showToast && (
<div className={`absolute top-6 left-1/2 transform -translate-x-1/2 px-4 py-3 rounded-full shadow-xl flex items-center space-x-2 z-50 animate-bounce-in ${
showToast.type === 'error' ? 'bg-red-500 text-white' : 'bg-slate-800 text-white'
}`}>
{showToast.type === 'success' ? <Check size={16} /> : <X size={16} />}
<span className="text-sm font-medium">{showToast.msg}</span>
</div>
)}
</div>
<style>{`
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
.animate-fade-in {
animation: fade-in 0.3s ease-out;
}
@keyframes slide-up {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
.animate-slide-up {
animation: slide-up 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes bounce-in {
0% { transform: translate(-50%, -20px); opacity: 0; }
50% { transform: translate(-50%, 5px); opacity: 1; }
100% { transform: translate(-50%, 0); }
}
.animate-bounce-in {
animation: bounce-in 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
`}</style>
</div>
);
}