import React, { useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { Card } from '../components/Card'; import { ProgressBar } from '../components/ProgressBar'; import { Badge } from '../components/Badge'; import { useConfig, externalApiFetch } from '../hooks/useApi'; import { CHECKLIST_DATA } from '@cra-compliance/shared'; import { useCompliance } from '../context/ComplianceContext'; import type { DashboardStats } from '@cra-compliance/types'; interface DashboardProps { onNavigate: (page: 'checklist' | 'documents' | 'scanner' | 'incidents' | 'education' | 'settings') => void; } export function Dashboard({ onNavigate }: DashboardProps) { const config = useConfig(); const { progress, loading: checklistLoading, totalItems, completedItems, compliancePercent } = useCompliance(); const isPremium = config.plan !== 'free'; const statsQuery = useQuery({ queryKey: ['dashboard'], enabled: isPremium, queryFn: async () => { const res = await externalApiFetch<{ data: DashboardStats }>('/api/dashboard'); return res.data; }, staleTime: 5 * 60 * 1000, refetchInterval: 5 * 60 * 1000, }); const stats = statsQuery.data ?? null; // CRA deadline info const reportingDeadline = new Date('2026-09-11'); const fullDeadline = new Date('2027-12-11'); const now = new Date(); const daysUntilReporting = Math.max(0, Math.ceil((reportingDeadline.getTime() - now.getTime()) / 86400000)); const daysUntilFull = Math.max(0, Math.ceil((fullDeadline.getTime() - now.getTime()) / 86400000)); const reportingPassed = reportingDeadline < now; const fullPassed = fullDeadline < now; // Score: for paid users use API score, for free use checklist % const displayScore = stats ? stats.compliance_score : compliancePercent; const scoreColor = displayScore >= 80 ? 'var(--cra-green)' : displayScore >= 50 ? 'var(--cra-yellow)' : 'var(--cra-red)'; // Review nudge: free users, >50% checklist, 30+ days since first load, not dismissed const FIRST_LOAD_KEY = 'resicoma_first_load'; const REVIEW_DISMISSED_KEY = 'resicoma_review_dismissed'; const [reviewDismissed, setReviewDismissed] = useState(() => !!localStorage.getItem(REVIEW_DISMISSED_KEY)); // Record first load timestamp on mount if (!localStorage.getItem(FIRST_LOAD_KEY)) { localStorage.setItem(FIRST_LOAD_KEY, String(Date.now())); } const firstLoadMs = Number(localStorage.getItem(FIRST_LOAD_KEY) || Date.now()); const daysSinceFirstLoad = (Date.now() - firstLoadMs) / 86400000; const showReviewNudge = !isPremium && !reviewDismissed && compliancePercent >= 50 && daysSinceFirstLoad >= 30; if (checklistLoading) { return
Loading dashboard...
; } return (

Dashboard

CRA reporting obligations: {reportingPassed ? 'active now' : `${daysUntilReporting} days away`} {!fullPassed && ` · Full compliance: ${daysUntilFull} days`}

{reportingPassed ? 'Deadline Active' : `${daysUntilReporting}d to report deadline`}
{/* Overdue incident urgent banner */} {stats && stats.overdue_incidents > 0 && (
{stats.overdue_incidents} overdue incident{stats.overdue_incidents !== 1 ? 's' : ''}. {' '}CRA Article 14 requires notification to your national CSIRT within 24 hours of awareness.
)} {/* Critical vuln banner */} {stats && stats.critical_vulnerabilities > 0 && (
{stats.critical_vulnerabilities} critical vulnerability{stats.critical_vulnerabilities !== 1 ? 'ies' : 'y'}. {' '}Review and remediate, or start an incident if actively exploited.
)} {/* Stats Grid */}
{displayScore}%
Compliance Score
{isPremium && stats ? ( ) : (

Based on checklist only. Upgrade for full score including vuln data.

)}
{completedItems}/{totalItems}
Checklist Complete
{isPremium && stats ? ( <>
0 ? 'var(--cra-red)' : 'var(--cra-green)' }}> {stats.open_vulnerabilities}
Open Vulnerabilities
{stats.critical_vulnerabilities > 0 && (

{stats.critical_vulnerabilities} critical

)}
0 ? 'var(--cra-red)' : stats.open_incidents > 0 ? 'var(--cra-yellow)' : 'var(--cra-green)' }}> {stats.open_incidents}
Open Incidents
{stats.overdue_incidents > 0 && (

{stats.overdue_incidents} overdue

)}
) : ( <>
onNavigate('scanner')}>
—
Open Vulnerabilities

Upgrade to scan

{config.plan.toUpperCase()}
Current Plan
)}
{/* Scan + incident summary for paid users */} {isPremium && stats && (
onNavigate('scanner')}>Go to Scanner → }>
{[ { label: 'Critical', count: stats.critical_vulnerabilities, color: 'var(--cra-red)' }, { label: 'High', count: stats.high_vulnerabilities, color: 'var(--cra-orange)' }, { label: 'Medium', count: stats.medium_vulnerabilities, color: 'var(--cra-yellow)' }, { label: 'Total Open', count: stats.open_vulnerabilities, color: 'var(--cra-text)' }, ].map(({ label, count, color }) => (
{count} {label}
))}
{stats.last_scan_at ? (

Last scan: {new Date(stats.last_scan_at).toLocaleString()} {stats.next_scan_at && ` · Next: ${new Date(stats.next_scan_at).toLocaleString()}`}

) : (

No scan yet. Go to the Scanner to add plugins and run your first scan.

)}
onNavigate('incidents')}>Go to Incidents → }> {stats.open_incidents === 0 ? (

No open incidents. Create one from a vulnerability when active exploitation is confirmed.

) : (
0 ? 'var(--cra-yellow)' : 'var(--cra-green)' }}>{stats.open_incidents} Open
0 ? 'var(--cra-red)' : 'var(--cra-green)' }}>{stats.overdue_incidents} Overdue
{stats.most_urgent_deadline_at && (

Most urgent deadline: {new Date(stats.most_urgent_deadline_at).toLocaleString()}

)}
)}
)} {/* Quick Actions + Category Progress side by side */}

Quick Actions

{CHECKLIST_DATA.map((category) => { const catCompleted = category.items.filter((item) => progress[item.id]?.completed).length; const catTotal = category.items.length; return (
); })}
{/* WordPress.org review nudge */} {showReviewNudge && (
⭐
Enjoying ResilienceWP?

You've completed {compliancePercent}% of your CRA checklist. If this plugin has helped you, a review on WordPress.org takes 30 seconds and means a lot.

{ localStorage.setItem(REVIEW_DISMISSED_KEY, '1'); setReviewDismissed(true); }} > Leave a review →
)} {/* Upgrade CTA for free users */} {!isPremium && (

Automate Your CRA Compliance

The free plan covers paperwork. Paid plans cover the ongoing obligations that have fines attached.

{[ ['Compliance checklist', true, true, true], ['Document generator', true, true, true], ['CRA education guide', true, true, true], ['Vulnerability scanning', false, '5 plugins, weekly', '20 plugins, daily'], ['Email alerts', false, true, true], ['Incident Center (24h timer)', false, true, true], ['Evidence export', false, false, true], ['Webhook integrations', false, false, true], ['Priority support', false, false, true], ].map(([feature, free, basic, pro]) => ( {[free, basic, pro].map((val, i) => ( ))} ))}
Feature Free Basic $49/yr Pro $99/yr
{feature} {val === true ? ✓ : val === false ? — : {val}}
View Pricing →
)}
); } function ScoreBreakdown({ stats }: { stats: DashboardStats }) { const [open, setOpen] = useState(false); const deductions: { label: string; value: number; per: string; color: string }[] = [ { label: 'Critical vulns', value: stats.critical_vulnerabilities, per: '−15 pts each', color: 'var(--cra-red)' }, { label: 'High vulns', value: stats.high_vulnerabilities, per: '−7 pts each', color: 'var(--cra-orange)' }, { label: 'Medium vulns', value: stats.medium_vulnerabilities, per: '−3 pts each', color: 'var(--cra-yellow)' }, { label: 'Overdue incidents', value: stats.overdue_incidents, per: '−20 pts each', color: 'var(--cra-red)' }, { label: 'Open incidents', value: stats.open_incidents - stats.overdue_incidents, per: '−5 pts each', color: 'var(--cra-yellow)' }, ].filter((d) => d.value > 0); if (deductions.length === 0) { return (

No active deductions — well done.

); } return (
{open && (
{deductions.map((d) => (
{d.value}× {d.label} {d.per}
))}
)}
); }