import React, { useCallback, useEffect, useState } from 'react'; import { useAuth } from '@clerk/clerk-react'; import { useBacklinkOutreachStore } from '../../stores/backlinkOutreachStore'; import { listEmailTemplates, generateEmailTemplate, generateSubjectLines, generateFollowUp, personalizeEmail, createEmailTemplate, EmailTemplateRecord, GenerateEmailRequest, bulkUpdateLeadStatus, updateLeadStatus, fetchCampaignAnalyticsVolume, fetchCampaignAnalyticsFunnel, CampaignVolumePoint, FunnelStage, exportCampaignLeadsCsv, exportCampaignAttemptsCsv, exportCampaignRepliesCsv, } from '../../api/backlinkOutreachApi'; import { showToastNotification } from '../../utils/toastNotifications'; import { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer } from 'recharts'; type Tab = 'campaigns' | 'discover' | 'leads' | 'composer' | 'analytics'; const STATUS_OPTIONS = ['discovered', 'contacted', 'replied', 'placed', 'bounced', 'unsubscribed']; const STATUS_EXPLANATIONS: Record = { discovered: 'Lead found but not yet contacted', contacted: 'Outreach email has been sent', replied: 'Lead has responded to outreach', placed: 'Guest post successfully published', bounced: 'Email bounced — invalid or inactive', unsubscribed: 'Lead opted out of future emails', }; const GRADIENT_BG = 'linear-gradient(135deg, #0f0c29, #302b63, #24243e)'; const GRADIENT_CARD = 'linear-gradient(135deg, rgba(255,255,255,0.08), rgba(255,255,255,0.03))'; const GRADIENT_PRIMARY = 'linear-gradient(135deg, #667eea, #764ba2)'; const GRADIENT_SECONDARY = 'linear-gradient(135deg, #f093fb, #f5576c)'; const GRADIENT_SUCCESS = 'linear-gradient(135deg, #43e97b, #38f9d7)'; const GRADIENT_WARNING = 'linear-gradient(135deg, #fa709a, #fee140)'; const TooltipWrap: React.FC<{ text: string; children: React.ReactNode }> = ({ text, children }) => { const [show, setShow] = useState(false); return ( setShow(true)} onMouseLeave={() => setShow(false)}> {children} {show && ( {text} )} ); }; const cardSx: React.CSSProperties = { background: GRADIENT_CARD, backdropFilter: 'blur(20px)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '12px', boxShadow: '0 8px 32px rgba(0,0,0,0.15)', }; const inputSx: React.CSSProperties = { width: '100%', padding: '12px 16px', background: 'rgba(255,255,255,0.06)', border: '1px solid rgba(255,255,255,0.12)', borderRadius: '8px', color: '#fff', fontSize: '14px', outline: 'none', }; const selectSx: React.CSSProperties = { ...inputSx, cursor: 'pointer', }; const btnBase: React.CSSProperties = { border: 'none', borderRadius: '8px', cursor: 'pointer', fontWeight: 600, fontSize: '14px', padding: '10px 24px', transition: 'all 0.2s', }; const BacklinkOutreachDashboard: React.FC = () => { const { userId } = useAuth(); const workspaceId = userId || 'default'; const { campaigns, selectedCampaign, discoveredOpportunities, isLoading, isDiscovering, error, fetchCampaigns, createCampaign, selectCampaign, deepDiscover, clearDiscoveries, attempts, replies, followups, analytics, fetchAttempts, fetchReplies, fetchFollowUps, fetchAnalytics, } = useBacklinkOutreachStore(); const [activeTab, setActiveTab] = useState('campaigns'); const [newCampaignName, setNewCampaignName] = useState(''); const [keyword, setKeyword] = useState(''); const [discoverCampaignId, setDiscoverCampaignId] = useState(''); const [templates, setTemplates] = useState([]); const [selectedTemplateId, setSelectedTemplateId] = useState(''); const [topic, setTopic] = useState(''); const [targetSite, setTargetSite] = useState(''); const [tone, setTone] = useState<'professional' | 'friendly' | 'casual' | 'formal'>('professional'); const [subject, setSubject] = useState(''); const [body, setBody] = useState(''); const [subjectSuggestions, setSubjectSuggestions] = useState([]); const [isGenerating, setIsGenerating] = useState(false); const [leadName, setLeadName] = useState(''); const [leadSite, setLeadSite] = useState(''); const [leadContentTopic, setLeadContentTopic] = useState(''); const [followUpDays, setFollowUpDays] = useState(7); const [replyContext, setReplyContext] = useState(''); const [templateName, setTemplateName] = useState(''); const [selectedLeadIds, setSelectedLeadIds] = useState>(new Set()); const [bulkStatus, setBulkStatus] = useState('contacted'); const [volumeData, setVolumeData] = useState([]); const [funnelData, setFunnelData] = useState([]); const [analyticsDays, setAnalyticsDays] = useState(30); const [isAnalyticsLoading, setIsAnalyticsLoading] = useState(false); const [isStatusUpdating, setIsStatusUpdating] = useState(false); const [isExporting, setIsExporting] = useState(null); useEffect(() => { fetchCampaigns(workspaceId); }, [fetchCampaigns, workspaceId]); useEffect(() => { listEmailTemplates().then(r => setTemplates(r.templates)).catch(() => showToastNotification('Failed to load email templates', 'error')); }, []); useEffect(() => { if (selectedCampaign) { const cid = selectedCampaign.campaign_id; fetchAttempts(cid); fetchReplies(cid); fetchFollowUps(cid); fetchAnalytics(cid); } }, [selectedCampaign, fetchAttempts, fetchReplies, fetchFollowUps, fetchAnalytics]); useEffect(() => { if (!selectedCampaign) return; let cancelled = false; setIsAnalyticsLoading(true); Promise.all([ fetchCampaignAnalyticsVolume(selectedCampaign.campaign_id, analyticsDays), fetchCampaignAnalyticsFunnel(selectedCampaign.campaign_id), ]).then(([vol, funnel]) => { if (!cancelled) { setVolumeData(vol.volume); setFunnelData(funnel.stages); setIsAnalyticsLoading(false); } }).catch(() => { if (!cancelled) { showToastNotification('Failed to load analytics data', 'error'); setIsAnalyticsLoading(false); } }); return () => { cancelled = true; }; }, [analyticsDays, selectedCampaign?.campaign_id]); const handleCreateCampaign = useCallback(async () => { if (!newCampaignName.trim()) return; const id = await createCampaign(workspaceId, newCampaignName.trim()); if (id) { setNewCampaignName(''); setActiveTab('discover'); } }, [newCampaignName, createCampaign]); const handleDiscover = useCallback(async () => { if (!keyword.trim()) return; await deepDiscover(keyword.trim(), 15); }, [keyword, deepDiscover]); const handleDiscoverAndSave = useCallback(async () => { if (!keyword.trim() || !discoverCampaignId) return; await deepDiscover(keyword.trim(), 15, discoverCampaignId); }, [keyword, discoverCampaignId, deepDiscover]); const handleSelectCampaign = useCallback(async (campaignId: string) => { await selectCampaign(campaignId); setActiveTab('leads'); }, [selectCampaign]); const handleGenerate = useCallback(async () => { if (!topic.trim()) return; setIsGenerating(true); try { const payload: GenerateEmailRequest = { topic: topic.trim(), target_site: targetSite.trim() || undefined, tone, existing_template_id: selectedTemplateId || undefined, }; const result = await generateEmailTemplate(payload); setSubject(result.subject); setBody(result.body); setSubjectSuggestions([]); } catch (e) { showToastNotification('Email generation failed', 'error'); } finally { setIsGenerating(false); } }, [topic, targetSite, tone, selectedTemplateId]); const handleSuggestSubjects = useCallback(async () => { if (!body.trim()) return; setIsGenerating(true); try { const result = await generateSubjectLines({ body: body.trim() }); setSubjectSuggestions(result.subjects); } catch (e) { showToastNotification('Failed to generate subject lines', 'error'); } finally { setIsGenerating(false); } }, [body]); const handlePersonalize = useCallback(async () => { if (!leadName.trim() || !leadSite.trim() || !leadContentTopic.trim() || !topic.trim()) return; setIsGenerating(true); try { const result = await personalizeEmail({ lead_name: leadName.trim(), lead_site: leadSite.trim(), lead_content_topic: leadContentTopic.trim(), pitch_topic: topic.trim(), existing_body: body, }); setSubject(result.subject); setBody(result.body); } catch (e) { showToastNotification('Personalization failed', 'error'); } finally { setIsGenerating(false); } }, [leadName, leadSite, leadContentTopic, topic, body]); const handleFollowUp = useCallback(async () => { if (!subject.trim() || !body.trim()) return; setIsGenerating(true); try { const result = await generateFollowUp({ original_subject: subject.trim(), original_body: body.trim(), days_elapsed: followUpDays, reply_context: replyContext.trim() || undefined, }); setSubject(result.subject); setBody(result.body); } catch (e) { showToastNotification('Follow-up generation failed', 'error'); } finally { setIsGenerating(false); } }, [subject, body, followUpDays, replyContext]); const handleSaveTemplate = useCallback(async () => { if (!templateName.trim() || !subject.trim() || !body.trim()) return; try { await createEmailTemplate({ name: templateName.trim(), subject_template: subject, body_template: body, variables: ['lead_name', 'lead_site', 'pitch_topic'], }); setTemplateName(''); const updated = await listEmailTemplates(); setTemplates(updated.templates); } catch (e) { showToastNotification('Failed to save template', 'error'); } }, [templateName, subject, body]); const applySuggestion = (s: string) => { setSubject(s); setSubjectSuggestions([]); }; const toggleLeadSelection = (leadId: string) => { setSelectedLeadIds(prev => { const next = new Set(prev); if (next.has(leadId)) next.delete(leadId); else next.add(leadId); return next; }); }; const toggleAllLeads = () => { if (!selectedCampaign) return; const all = selectedCampaign.leads; setSelectedLeadIds(prev => prev.size === all.length ? new Set() : new Set(all.map(l => l.lead_id)) ); }; const handleSingleStatusUpdate = async (leadId: string, status: string) => { setIsStatusUpdating(true); try { await updateLeadStatus(leadId, { status }); showToastNotification(`Status updated to "${status}"`, 'success'); await selectCampaign(selectedCampaign!.campaign_id); } catch (e) { showToastNotification('Status update failed', 'error'); } finally { setIsStatusUpdating(false); } }; const handleBulkStatusUpdate = async () => { if (selectedLeadIds.size === 0) return; setIsStatusUpdating(true); try { const result = await bulkUpdateLeadStatus({ lead_ids: Array.from(selectedLeadIds), status: bulkStatus }); if (result.failed.length > 0) { showToastNotification(`Updated ${result.updated} leads; ${result.failed.length} failed`, 'warning'); } else { showToastNotification(`Updated ${result.updated} leads to "${bulkStatus}"`, 'success'); } setSelectedLeadIds(new Set()); await selectCampaign(selectedCampaign!.campaign_id); } catch (e) { showToastNotification('Bulk status update failed', 'error'); } finally { setIsStatusUpdating(false); } }; const handleExportCsv = useCallback(async (type: 'leads' | 'attempts' | 'replies') => { if (!selectedCampaign || isExporting) return; setIsExporting(type); try { const fn = type === 'leads' ? exportCampaignLeadsCsv : type === 'attempts' ? exportCampaignAttemptsCsv : exportCampaignRepliesCsv; const blob = await fn(selectedCampaign.campaign_id); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${type}_${selectedCampaign.campaign_id}.csv`; a.click(); window.URL.revokeObjectURL(url); showToastNotification(`${type.charAt(0).toUpperCase() + type.slice(1)} exported`, 'success'); } catch (e: any) { showToastNotification(e?.message || 'Export failed', 'error'); } finally { setIsExporting(null); } }, [selectedCampaign, isExporting]); const handleTabChange = useCallback((tab: Tab) => { setActiveTab(tab); }, []); const renderStatusBadge = (status: string) => { const styles: Record = { discovered: { bg: 'rgba(102,126,234,0.2)', fg: '#8b9cf7' }, contacted: { bg: 'rgba(240,147,251,0.2)', fg: '#f093fb' }, replied: { bg: 'rgba(67,233,123,0.2)', fg: '#43e97b' }, placed: { bg: 'rgba(67,233,123,0.3)', fg: '#38f9d7' }, bounced: { bg: 'rgba(245,87,108,0.2)', fg: '#f5576c' }, unsubscribed: { bg: 'rgba(254,225,64,0.15)', fg: '#fee140' }, }; const s = styles[status] || { bg: 'rgba(255,255,255,0.1)', fg: '#aaa' }; return ( {status} ); }; const tabMeta: { key: Tab; label: string; desc: string }[] = [ { key: 'campaigns', label: 'Campaigns', desc: 'Create and manage outreach campaigns' }, { key: 'discover', label: 'Discover', desc: 'AI-powered search for guest post opportunities' }, { key: 'leads', label: 'Leads', desc: 'Track leads, send outreach, and manage replies' }, { key: 'composer', label: 'Composer', desc: 'AI email composer with smart suggestions' }, { key: 'analytics', label: 'Analytics', desc: 'Campaign performance metrics and exports' }, ]; const SectionHeader: React.FC<{ title: string; subtitle: string }> = ({ title, subtitle }) => (

{title}

{subtitle}

); return (
{/* Header */}

Backlink Outreach

AI-powered guest post outreach platform — discover opportunities, manage campaigns, compose emails, and track results.

{/* Tab bar */}
{tabMeta.map(({ key, label, desc }) => ( ))}
{error && (
{error}
)} {/* === CAMPAIGNS TAB === */} {activeTab === 'campaigns' && (
setNewCampaignName(e.target.value)} placeholder="Enter campaign name (e.g. 'Tech Bloggers Q3')" style={{ ...inputSx, flex: 1 }} />
{campaigns.length === 0 && !isLoading && (

No campaigns yet. Create one above to get started.

)} {campaigns.map((c) => (
handleSelectCampaign(c.campaign_id)} style={{ padding: '16px', marginBottom: '8px', borderRadius: '10px', cursor: 'pointer', background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.08)', transition: 'all 0.2s', }}>
{c.name}
Status: {c.status} {c.created_at && <> · Created {new Date(c.created_at).toLocaleDateString()}}
))} {isLoading &&

Loading...

}
)} {/* === DISCOVER TAB === */} {activeTab === 'discover' && (
setKeyword(e.target.value)} placeholder="e.g. 'AI marketing', 'SaaS growth', 'digital nomad'" style={{ ...inputSx, flex: 1, minWidth: '220px' }} />
{isDiscovering && (

Searching across Exa (neural) + DuckDuckGo... This may take 10–20 seconds.

)} {discoveredOpportunities.length > 0 && (
{discoveredOpportunities.length} opportunities found
{discoveredOpportunities.map((opp, i) => (
{opp.domain}
{opp.snippet &&
{opp.snippet.slice(0, 200)}...
}
Quality: {(opp.quality_score * 100).toFixed(0)}% Confidence: {(opp.confidence_score * 100).toFixed(0)}% {opp.has_guest_post_guidelines && ( Has guidelines )} {opp.email && ( Email found )}
))}
)} {!isDiscovering && discoveredOpportunities.length === 0 && (

Enter a keyword above and click Discover to find guest post opportunities.

)}
)} {/* === LEADS TAB === */} {activeTab === 'leads' && (
{selectedCampaign ? (

{selectedCampaign.name}

{selectedCampaign.lead_count} leads · Status: {selectedCampaign.status}

{/* Analytics cards */} {analytics && (
{[{ label: 'Sent', value: analytics.send_volume, grad: GRADIENT_PRIMARY }, { label: 'Response Rate', value: `${(analytics.response_rate * 100).toFixed(1)}%`, grad: GRADIENT_SUCCESS }, { label: 'Replies', value: analytics.reply_count, grad: GRADIENT_WARNING }, { label: 'Placement', value: `${(analytics.placement_rate * 100).toFixed(1)}%`, grad: 'linear-gradient(135deg, #a18cd1, #fbc2eb)' }, { label: 'Blocked', value: analytics.blocked_count, grad: GRADIENT_SECONDARY }, ].map(({ label, value, grad }) => (
{value}
{label}
))}
)} {/* Reply classification */} {analytics && Object.keys(analytics.reply_classification).length > 0 && (
Reply Classification
{Object.entries(analytics.reply_classification).map(([cls, count]) => ( {cls}: {count} ))}
)} {/* Bulk actions */} {selectedCampaign.leads.length > 0 && (
{selectedLeadIds.size > 0 && ( <> )}
)} {selectedCampaign.leads.length === 0 && (

No leads yet. Go to the Discover tab to find and save opportunities.

)} {/* Lead cards */} {selectedCampaign.leads.map((lead) => (
toggleLeadSelection(lead.lead_id)} style={{ marginTop: '4px', accentColor: '#667eea' }} />
{lead.page_title || lead.domain}
{lead.url && {lead.url}}
{renderStatusBadge(lead.status)} {lead.email && Email: {lead.email}} Source: {lead.discovery_source}
{STATUS_OPTIONS.map((s) => ( ))}
{attempts.filter(a => a.lead_id === lead.lead_id).slice(0, 1).map(a => (
Latest: {a.subject} — {renderStatusBadge(a.status)} {a.sent_at && {new Date(a.sent_at).toLocaleString()}}
))}
))} {/* Attempt history */} {attempts.length > 0 && (
{['Subject', 'Status', 'Sender', 'Sent At'].map(h => ( ))} {attempts.map((a) => ( ))}
{h}
{a.subject} {renderStatusBadge(a.status)} {a.sender_email} {a.sent_at ? new Date(a.sent_at).toLocaleDateString() : '-'}
)} {/* Reply inbox */} {replies.length > 0 && (
{replies.map((r) => (
{r.subject} {r.classification}
From: {r.from_email} · {r.received_at ? new Date(r.received_at).toLocaleString() : ''}
{r.body.slice(0, 300)}
))}
)} {/* Follow-up schedule */} {followups.length > 0 && (
{followups.map((f) => (
{f.subject}
{f.scheduled_for && {new Date(f.scheduled_for).toLocaleDateString()}} {f.sent ? 'Sent' : 'Pending'}
))}
)}
) : (

Select a campaign from the Campaigns tab to view its leads.

)}
)} {/* === COMPOSER TAB === */} {activeTab === 'composer' && (
setTopic(e.target.value)} placeholder="e.g. AI marketing trends, SaaS growth strategies" style={inputSx} />
setTargetSite(e.target.value)} placeholder="e.g. example.com" style={inputSx} />
setSubject(e.target.value)} placeholder="Email subject line" style={inputSx} /> {subjectSuggestions.length > 0 && (
Click a suggestion to apply
{subjectSuggestions.map((s, i) => (
applySuggestion(s)} style={{ padding: '6px 10px', cursor: 'pointer', borderRadius: '6px', fontSize: '13px', color: '#8b9cf7', transition: 'background 0.2s' }}> {s}
))}
)}