/* global React, Icon, StatusPill, fmtAED */
const { useState } = React;
/* ============================================================
LINKEDIN CAMPAIGN — list + builder
Connection campaigns: pick a lead list, set a daily cap,
send N connection requests/day until the list is finished.
============================================================ */
const liTh = { padding: '10px 20px', textAlign: 'left', fontSize: 9, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'var(--text-dim)' };
const liTd = { padding: '12px 20px', verticalAlign: 'middle', color: 'var(--text-secondary)' };
function LinkedInCampaigns({ goto }) {
return (
Outreach · LinkedIn
LinkedIn Campaign
{window.LINKEDIN_CAMPAIGNS.length} campaigns · {window.LINKEDIN_CAMPAIGNS.filter(c => c.status === 'active').length} active
| Name |
Lead List |
Status |
Daily Cap |
Progress |
Accepted |
Replied |
|
{window.LINKEDIN_CAMPAIGNS.map(c => {
const list = window.LEAD_LISTS.find(l => l.id === c.listId);
const total = list?.count || 0;
const pct = total ? Math.round((c.sent / total) * 100) : 0;
const acceptRate = c.sent ? ((c.accepted / c.sent) * 100).toFixed(1) : '0.0';
const replyRate = c.accepted ? ((c.replied / c.accepted) * 100).toFixed(1) : '0.0';
return (
goto('linkedin-builder', c.id)} style={{ borderBottom: '1px solid var(--divider)', cursor: 'pointer' }}>
|
{c.name}
{c.account}
|
{list?.name || '—'}
{total} leads
|
|
{c.dailyCap}/day |
|
{c.accepted} ({acceptRate}%)
|
{c.replied} ({replyRate}%)
|
|
);
})}
);
}
function LinkedInCampaignBuilder({ campaignId, goto }) {
const c = window.LINKEDIN_CAMPAIGNS.find(x => x.id === campaignId) || window.LINKEDIN_CAMPAIGNS[0];
const [listId, setListId] = useState(c.listId);
const [cap, setCap] = useState(c.dailyCap);
const [account, setAccount] = useState(c.account);
const list = window.LEAD_LISTS.find(l => l.id === listId);
const total = list?.count || 0;
const pct = total ? Math.round((c.sent / total) * 100) : 0;
const remaining = Math.max(0, total - c.sent);
const eta = cap > 0 ? Math.ceil(remaining / cap) : 0;
const acceptRate = c.sent ? ((c.accepted / c.sent) * 100).toFixed(1) : '0.0';
return (
{c.name}
{c.status === 'draft' && }
{c.status === 'active' && }
{/* LIVE PROGRESS — shown when active/completed */}
{(c.status === 'active' || c.status === 'completed') && (
Progress
{pct}% complete
{remaining} remaining
)}
{/* SETUP */}
1
Lead List
Which list to send connection requests from
{window.LEAD_LISTS.map(l => (
))}
{/* SENDING ACCOUNT */}
2
Sending Account
Which connected LinkedIn account sends from
{/* DAILY CAP + INVITE NOTE */}
3
Daily Send Cap
Connection requests per day until the list is finished
5 (safe)
20 (recommended)
50 (aggressive)
Sending {cap} per day to {total} leads
{' · finishes in '}
~{Math.ceil(total / cap)} days
{/* INVITE MESSAGE */}
4
Invite Message
Optional · 200 char max · sent with the connection request
{'{{first_name}}'} · {'{{company}}'} · {'{{title}}'}
);
}
const liStep = {
width: 28, height: 28,
background: 'var(--accent-bg)', color: 'var(--accent)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontFamily: 'var(--font-mono)', fontSize: 12, fontWeight: 600,
};
function Stat({ label, value, sub, accent }) {
return (
{label}
{value}
{sub &&
{sub}
}
);
}
Object.assign(window, { LinkedInCampaigns, LinkedInCampaignBuilder });