TAM / frontend /src /components /ImportWizard.jsx
Claude
Redesign to monday.com light theme + add project management
3c4a809 unverified
Raw
History Blame Contribute Delete
7.61 kB
import { useState, useRef } from 'react'
import { importApi } from '../api'
import { useProject } from '../contexts/ProjectContext'
const TABS = [
{ id: 'csv', label: 'πŸ“„ CSV File', desc: 'Import any CSV with company/contact data' },
{ id: 'excel', label: 'πŸ“Š Excel File', desc: 'Import .xlsx or .xls spreadsheets' },
{ id: 'linkedin', label: 'πŸ”— LinkedIn Export', desc: 'Import LinkedIn connections or Sales Navigator CSV export' },
]
function DropZone({ accept, onFile }) {
const [dragging, setDragging] = useState(false)
const inputRef = useRef()
const handleDrop = (e) => {
e.preventDefault()
setDragging(false)
const file = e.dataTransfer.files[0]
if (file) onFile(file)
}
return (
<div
onClick={() => inputRef.current.click()}
onDragOver={e => { e.preventDefault(); setDragging(true) }}
onDragLeave={() => setDragging(false)}
onDrop={handleDrop}
style={{
border: `2px dashed ${dragging ? 'var(--accent)' : 'var(--border)'}`,
borderRadius: 'var(--radius)',
padding: '40px 24px',
textAlign: 'center',
cursor: 'pointer',
background: dragging ? 'rgba(108,99,255,0.05)' : 'transparent',
transition: 'all 0.15s',
}}
>
<div style={{ fontSize: 36, marginBottom: 8 }}>⬆</div>
<div style={{ fontWeight: 600, marginBottom: 4 }}>Drop file here or click to browse</div>
<div style={{ color: 'var(--text-muted)', fontSize: 13 }}>{accept}</div>
<input ref={inputRef} type="file" accept={accept} style={{ display: 'none' }} onChange={e => e.target.files[0] && onFile(e.target.files[0])} />
</div>
)
}
function LinkedInTip() {
return (
<div className="alert alert-info" style={{ marginBottom: 16 }}>
<strong>How to export from LinkedIn:</strong>
<ol style={{ marginTop: 8, paddingLeft: 20, display: 'flex', flexDirection: 'column', gap: 4 }}>
<li>Go to LinkedIn Settings β†’ Data Privacy β†’ Get a copy of your data</li>
<li>Select "Connections" and request the archive</li>
<li>Download and upload the <code>Connections.csv</code> file here</li>
<li>For Sales Navigator: Export leads directly as CSV from your lead lists</li>
</ol>
</div>
)
}
export default function ImportWizard() {
const { activeProject } = useProject()
const [tab, setTab] = useState('csv')
const [file, setFile] = useState(null)
const [result, setResult] = useState(null)
const [error, setError] = useState(null)
const [loading, setLoading] = useState(false)
const handleFile = (f) => {
setFile(f)
setResult(null)
setError(null)
}
const handleImport = async () => {
if (!file) return
setLoading(true)
setResult(null)
setError(null)
try {
const pid = activeProject?.id ?? null
let res
if (tab === 'csv') res = await importApi.csv(file, 'csv', pid)
else if (tab === 'excel') res = await importApi.excel(file, 'excel', pid)
else res = await importApi.linkedin(file, pid)
setResult(res)
setFile(null)
} catch (e) {
setError(e.message)
} finally {
setLoading(false)
}
}
const currentTab = TABS.find(t => t.id === tab)
return (
<div className="page">
<div className="page-header">
<div>
<h1 className="page-title">Import Leads</h1>
{activeProject && <div className="page-subtitle">Importing into: <strong>{activeProject.name}</strong></div>}
</div>
</div>
{/* Tabs */}
<div style={{ display: 'flex', gap: 8, marginBottom: 24 }}>
{TABS.map(t => (
<button
key={t.id}
onClick={() => { setTab(t.id); setFile(null); setResult(null); setError(null) }}
style={{
padding: '8px 16px',
borderRadius: 'var(--radius)',
background: tab === t.id ? 'var(--accent)' : 'var(--surface)',
color: tab === t.id ? '#fff' : 'var(--text-muted)',
border: '1px solid var(--border)',
fontWeight: tab === t.id ? 600 : 400,
}}
>
{t.label}
</button>
))}
</div>
<div className="card" style={{ maxWidth: 640 }}>
<p style={{ color: 'var(--text-muted)', marginBottom: 20 }}>{currentTab.desc}</p>
{tab === 'linkedin' && <LinkedInTip />}
{result && (
<div className="alert alert-success">
βœ… Successfully imported <strong>{result.imported}</strong> leads from {result.total_rows} rows.
{' '}<a href="/leads">View all leads β†’</a>
</div>
)}
{error && <div className="alert alert-error">❌ {error}</div>}
<DropZone
accept={tab === 'excel' ? '.xlsx,.xls' : '.csv'}
onFile={handleFile}
/>
{file && (
<div style={{ marginTop: 16, padding: '12px 16px', background: 'var(--surface2)', borderRadius: 'var(--radius)', display: 'flex', alignItems: 'center', gap: 12 }}>
<span style={{ fontSize: 24 }}>{tab === 'excel' ? 'πŸ“Š' : 'πŸ“„'}</span>
<div style={{ flex: 1 }}>
<div style={{ fontWeight: 500 }}>{file.name}</div>
<div style={{ color: 'var(--text-muted)', fontSize: 12 }}>{(file.size / 1024).toFixed(1)} KB</div>
</div>
<button className="btn-secondary" style={{ padding: '4px 10px' }} onClick={() => setFile(null)}>βœ•</button>
</div>
)}
<div style={{ marginTop: 16, display: 'flex', gap: 12 }}>
<button
className="btn-primary"
onClick={handleImport}
disabled={!file || loading}
style={{ minWidth: 140 }}
>
{loading ? <span className="spinner" /> : '⬆ Import'}
</button>
{result && (
<a href="/leads"><button className="btn-success">πŸ‘₯ View Leads</button></a>
)}
</div>
</div>
{/* Column mapping guide */}
<div className="card" style={{ maxWidth: 640, marginTop: 24 }}>
<div style={{ fontWeight: 600, marginBottom: 12 }}>Supported Column Names</div>
<div style={{ color: 'var(--text-muted)', fontSize: 13, lineHeight: 1.8 }}>
The importer auto-detects these column headers (case-insensitive):
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '4px 24px', marginTop: 8 }}>
{[
['Company / Organization / Account Name', 'β†’ company_name'],
['Name / Full Name / Contact Name', 'β†’ contact_name'],
['First Name + Last Name', 'β†’ combined contact_name'],
['Email / Email Address', 'β†’ email'],
['Phone / Mobile / Telephone', 'β†’ phone'],
['Website / URL', 'β†’ website'],
['Industry / Sector / Vertical', 'β†’ industry'],
['Company Size / Employees', 'β†’ company_size'],
['Location / City / Country', 'β†’ location'],
['LinkedIn / LinkedIn URL', 'β†’ linkedin_url'],
['Notes / Comments / Description', 'β†’ notes'],
].map(([k, v]) => (
<div key={k} style={{ display: 'contents' }}>
<span style={{ color: 'var(--text)' }}>{k}</span>
<span style={{ color: 'var(--accent)' }}>{v}</span>
</div>
))}
</div>
<div style={{ marginTop: 8 }}>Any other columns will be saved as custom fields.</div>
</div>
</div>
</div>
)
}