'use client'; import { useEffect, useMemo, useState } from 'react'; import { useRouter } from 'next/navigation'; import { Badge } from '@/components/ui/Badge'; import { Button } from '@/components/ui/Button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'; import { Input } from '@/components/ui/Input'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/Table'; import { BarChart3, Copy, Download, Filter, Loader2, Lock, LogOut, Mail, Search, TrendingUp, Users, } from 'lucide-react'; import { getGoalLabel, getRoleLabel, getSourceLabel, getTeamSizeLabel, getUseCaseLabel, } from '@/lib/revops'; type SegmentRow = { id: string; name: string | null; email: string; emailDomain: string | null; plan: string; lifecycleStage: string; lifecycleStageLabel: string; fitScore: number; intentScore: number; leadScore: number; signupSource: string | null; signupSourceLabel: string; signupSourceSelfReported: string | null; signupSourceSelfReportedLabel: string; signupCampaign: string | null; signupLandingPath: string | null; primaryUseCase: string | null; primaryUseCaseLabel: string; primaryGoal: string | null; primaryGoalLabel: string; jobRole: string | null; jobRoleLabel: string; companyName: string | null; teamSizeBucket: string | null; teamSizeLabel: string; upgradeBadges: string[]; createdAt: string; firstQrCreatedAt: string | null; activationAt: string | null; qrCount: number; dynamicQrCount: number; scanCount: number; }; type DashboardData = { overview: { totalUsers: number; mismatchCount: number; activatedUsers: number; paidUsers: number; }; acquisition: { bySource: Array<{ key: string; label: string; signups: number; firstQr: number; activated: number; hot: number; upgradeCandidates: number; paid: number; activationRate: number; }>; byCampaign: Array<{ key: string; signups: number; activated: number; paid: number; }>; byLandingPath: Array<{ key: string; signups: number; activated: number; paid: number; }>; }; funnel: { signup: number; sourceConfirmed: number; useCaseSelected: number; goalSelected: number; profileCaptured: number; firstQrCreated: number; firstDynamicQrCreated: number; activated: number; }; funnelBreakdowns: { bySource: Array; byUseCase: Array; byRole: Array; byTeamSize: Array; }; lifecycleSummary: Record; campaignSourceQuality: Array; upgradeCandidates: Array; filterOptions: { stages: string[]; sources: string[]; campaigns: string[]; landingPaths: string[]; useCases: string[]; goals: string[]; roles: string[]; teamSizes: string[]; plans: string[]; }; segments: { total: number; page: number; pageSize: number; totalPages: number; rows: SegmentRow[]; }; }; type Filters = { stage: string; source: string; campaign: string; landingPath: string; useCase: string; goal: string; role: string; teamSize: string; plan: string; search: string; sort: string; page: number; }; const defaultFilters: Filters = { stage: '', source: '', campaign: '', landingPath: '', useCase: '', goal: '', role: '', teamSize: '', plan: '', search: '', sort: 'leadScore_desc', page: 1, }; function buildQuery(filters: Filters) { const params = new URLSearchParams(); Object.entries(filters).forEach(([key, value]) => { if (value) { params.set(key, String(value)); } }); params.set('pageSize', '25'); return params.toString(); } function LifecycleCard({ label, value, active, onClick, }: { label: string; value: number; active: boolean; onClick: () => void; }) { return ( ); } function BreakdownTable({ title, rows, }: { title: string; rows: Array<{ label?: string; key: string; signups: number; activated: number; paid: number }>; }) { return ( {title} Segment Signups Activated Paid {rows.slice(0, 8).map((row) => ( {row.label || row.key} {row.signups} {row.activated} {row.paid} ))}
); } export default function NewsletterClient() { const router = useRouter(); const [isAuthenticated, setIsAuthenticated] = useState(false); const [isAuthenticating, setIsAuthenticating] = useState(true); const [loginError, setLoginError] = useState(''); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [filters, setFilters] = useState(defaultFilters); const queryString = useMemo(() => buildQuery(filters), [filters]); const fetchDashboard = async (query: string) => { setLoading(true); try { const response = await fetch(`/api/admin/revops?${query}`); if (!response.ok) { if (response.status === 401) { setIsAuthenticated(false); return; } throw new Error('Failed to load dashboard'); } const payload = await response.json(); setData(payload); setIsAuthenticated(true); } catch (error) { console.error(error); } finally { setLoading(false); setIsAuthenticating(false); } }; useEffect(() => { fetchDashboard(queryString); }, [queryString]); const handleLogin = async (event: React.FormEvent) => { event.preventDefault(); setLoginError(''); setIsAuthenticating(true); try { const response = await fetch('/api/newsletter/admin-login', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email, password }), }); if (!response.ok) { const body = await response.json(); setLoginError(body.error || 'Invalid credentials'); setIsAuthenticating(false); return; } await fetchDashboard(queryString); } catch (error) { setLoginError('Login failed. Please try again.'); setIsAuthenticating(false); } }; const handleLogout = async () => { await fetch('/api/auth/logout', { method: 'POST' }); setIsAuthenticated(false); setData(null); router.refresh(); }; const updateFilter = (key: keyof Filters, value: string | number) => { setFilters((current) => ({ ...current, [key]: value, page: key === 'page' ? Number(value) : 1, })); }; const resetFilters = () => setFilters(defaultFilters); const copyEmails = async () => { if (!data?.segments.rows.length) return; const text = data.segments.rows.map((row) => row.email).join(', '); await navigator.clipboard.writeText(text); }; const downloadCsv = () => { window.location.href = `/api/admin/revops?${queryString}&format=csv`; }; if (!isAuthenticated) { return (
QR Master Ops Cockpit

Internal access only.

setEmail(event.target.value)} autoComplete="off" required /> setPassword(event.target.value)} autoComplete="new-password" required /> {loginError &&

{loginError}

}
); } if (!data) { return (
); } const stageCards = [ { key: 'cold', label: 'Cold' }, { key: 'activated', label: 'Activated' }, { key: 'warm', label: 'Warm' }, { key: 'hot', label: 'Hot' }, { key: 'upgrade_candidate', label: 'Upgrade Candidate' }, { key: 'paid', label: 'Paid' }, ]; const funnelSteps = [ ['Signup', data.funnel.signup], ['Source confirmed', data.funnel.sourceConfirmed], ['Use case selected', data.funnel.useCaseSelected], ['Goal selected', data.funnel.goalSelected], ['Role/company/team captured', data.funnel.profileCaptured], ['First QR created', data.funnel.firstQrCreated], ['First dynamic QR created', data.funnel.firstDynamicQrCreated], ['Activated', data.funnel.activated], ]; return (

Ops Cockpit

Attribution, onboarding funnel, lifecycle quality, and filtered segments for QR Master.

Total users

{data.overview.totalUsers}

Activated users

{data.overview.activatedUsers}

Paid users

{data.overview.paidUsers}

Tracked vs self-reported mismatch

{data.overview.mismatchCount}

Acquisition Overview Source Signups First QR Activated Hot Upgrade Paid Activation rate {data.acquisition.bySource.map((row) => ( {row.label} {row.signups} {row.firstQr} {row.activated} {row.hot} {row.upgradeCandidates} {row.paid} {row.activationRate}% ))}
({ ...row, label: row.key || 'unknown' }))} /> ({ ...row, label: row.key || 'unknown' }))} />
Onboarding Funnel {funnelSteps.map(([label, value]) => { const percentage = data.funnel.signup ? Math.round((Number(value) / data.funnel.signup) * 100) : 0; return (
{label} {value} ({percentage}%)
); })}

Lifecycle Summary

Click a card to open the filtered list below.

{stageCards.map((card) => ( updateFilter('stage', filters.stage === card.key ? '' : card.key)} /> ))}
Full Segment Lists

Filter and export cold, activated, warm, hot, upgrade_candidate, and paid users.

updateFilter('search', event.target.value)} placeholder="Email, name, company" className="h-11 w-full rounded-xl border border-slate-300 bg-white pl-10 pr-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500" />
{data.segments.total} matching users {loading && }
Name Email Domain Plan Stage Fit Intent Lead Source Self-reported Campaign Landing page Use case Goal Role Company Team Created First QR Activated QRs Dynamic Scans {data.segments.rows.map((row) => ( {row.name || '—'} {row.email} {row.emailDomain || '—'} {row.plan} {row.lifecycleStageLabel} {row.fitScore} {row.intentScore} {row.leadScore} {row.signupSourceLabel} {row.signupSourceSelfReportedLabel} {row.signupCampaign || '—'} {row.signupLandingPath || '—'} {row.primaryUseCaseLabel} {row.primaryGoalLabel} {row.jobRoleLabel} {row.companyName || '—'} {row.teamSizeLabel} {new Date(row.createdAt).toLocaleDateString()} {row.firstQrCreatedAt ? new Date(row.firstQrCreatedAt).toLocaleDateString() : '—'} {row.activationAt ? new Date(row.activationAt).toLocaleDateString() : '—'} {row.qrCount} {row.dynamicQrCount} {row.scanCount} ))}
Page {data.segments.page} of {data.segments.totalPages}
Upgrade Candidates

Free users with strong fit and clear commercial intent.

User Lead score Use case Role Company Reasons {data.upgradeCandidates.map((row) => (
{row.name || row.email}
{row.email}
{row.leadScore} {getUseCaseLabel(row.primaryUseCase)} {getRoleLabel(row.jobRole)} {row.companyName || '—'}
{row.upgradeBadges.map((badge) => ( {badge} ))}
))}
); }