fintechdarkpatterns / src /app /components /OverviewPanel.tsx
Vezolu Vero
Initial commit with live data and Docker config
743409d
Raw
History Blame
6.74 kB
import { AlertTriangle, CheckCircle, XCircle, AlertCircle, Sparkles } from 'lucide-react';
import { AnalysisData } from '../types/analysis';
import { Progress } from './ui/progress';
import { Badge } from './ui/badge';
import { StatisticsBar } from './StatisticsBar';
interface OverviewPanelProps {
data: AnalysisData;
}
export function OverviewPanel({ data }: OverviewPanelProps) {
const getSeverityBadgeClass = (severity: string) => {
switch (severity) {
case 'critical':
return 'bg-red-500/10 text-red-400 border border-red-500/20';
case 'high':
return 'bg-orange-500/10 text-orange-400 border border-orange-500/20';
case 'medium':
return 'bg-amber-500/10 text-amber-400 border border-amber-500/20';
case 'low':
return 'bg-indigo-500/10 text-indigo-400 border border-indigo-500/20';
default:
return 'bg-slate-500/10 text-slate-400 border border-slate-500/20';
}
};
const getRiskIcon = () => {
switch (data.riskLevel) {
case 'critical':
return <XCircle className="w-10 h-10 text-rose-500" />;
case 'high':
return <AlertTriangle className="w-10 h-10 text-orange-500 animate-bounce" />;
case 'medium':
return <AlertCircle className="w-10 h-10 text-amber-500" />;
default:
return <CheckCircle className="w-10 h-10 text-emerald-500" />;
}
};
const getRiskColor = () => {
switch (data.riskLevel) {
case 'critical':
return 'from-rose-500/10 via-rose-500/5 to-transparent border-rose-500/30';
case 'high':
return 'from-orange-500/10 via-orange-500/5 to-transparent border-orange-500/30';
case 'medium':
return 'from-amber-500/10 via-amber-500/5 to-transparent border-amber-500/30';
default:
return 'from-emerald-500/10 via-emerald-500/5 to-transparent border-emerald-500/30';
}
};
const severityCounts = data.darkPatterns.reduce((acc, pattern) => {
acc[pattern.severity] = (acc[pattern.severity] || 0) + 1;
return acc;
}, {} as Record<string, number>);
const avgConfidence = data.darkPatterns.length > 0
? Math.round(data.darkPatterns.reduce((sum, p) => sum + p.confidence, 0) / data.darkPatterns.length)
: 0;
return (
<div className="space-y-6">
{/* Statistics Bar */}
<StatisticsBar
totalPatterns={data.darkPatterns.length}
criticalCount={severityCounts['critical'] || 0}
complianceScore={data.complianceReport.cfpbAlignment}
avgConfidence={avgConfidence}
/>
{/* Overall Risk Score */}
<div className={`p-6 rounded-2xl border bg-gradient-to-br ${getRiskColor()} relative overflow-hidden`}>
<div className="absolute top-0 right-0 w-48 h-48 bg-indigo-500/5 rounded-full blur-3xl pointer-events-none" />
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 mb-4">
<div className="flex items-center gap-4">
<div className="p-1 bg-slate-950/20 rounded-xl border border-white/5">
{getRiskIcon()}
</div>
<div>
<h3 className="text-xl font-extrabold text-slate-100 flex items-center gap-2">
Platform Trust Score: {data.overallScore}/100
</h3>
<p className="text-xs text-slate-400 mt-0.5">
Calculated Risk Rating:{' '}
<span className="font-extrabold uppercase text-indigo-400">
{data.riskLevel}
</span>
</p>
</div>
</div>
<div className="text-left sm:text-right bg-slate-950/20 px-4 py-2.5 rounded-xl border border-white/5">
<div className="text-2xl font-extrabold text-white leading-none">
{data.darkPatterns.length}
</div>
<div className="text-[10px] uppercase font-bold text-slate-400 tracking-wider mt-1">
Violations Found
</div>
</div>
</div>
<Progress value={data.overallScore} className="h-2.5 bg-slate-900 border border-white/5" />
</div>
<div className="grid md:grid-cols-2 gap-6">
{/* Pattern Types Detected */}
<div className="bg-slate-900/30 rounded-2xl p-6 border border-border">
<h4 className="font-bold text-sm text-slate-200 mb-4 flex items-center gap-2">
<Sparkles className="w-4 h-4 text-indigo-400" />
Detected Deceptive Categories
</h4>
{data.darkPatterns.length > 0 ? (
<div className="flex flex-wrap gap-2">
{Array.from(new Set(data.darkPatterns.map((p) => p.type))).map((type) => {
const pattern = data.darkPatterns.find((p) => p.type === type);
return (
<Badge
key={type}
variant="outline"
className={`${getSeverityBadgeClass(pattern?.severity || 'low')} text-[10px] font-bold px-3 py-1 rounded-md`}
>
{type}
</Badge>
);
})}
</div>
) : (
<p className="text-xs text-slate-500">No deceptive categories detected. Fully compliant!</p>
)}
</div>
{/* Quick Summary */}
<div className="bg-slate-900/30 rounded-2xl p-6 border border-border">
<h4 className="font-bold text-sm text-slate-200 mb-3">🤖 Explainable AI (XAI) Summary</h4>
<p className="text-xs text-slate-400 leading-relaxed">
{data.darkPatterns.length > 0 ? (
<>
This interface contains <strong>{data.darkPatterns.length} dark patterns</strong> flagged by the NLP vector classifiers.
Common patterns include <strong>{Array.from(new Set(data.darkPatterns.map(p => p.type))).slice(0, 3).join(', ')}</strong>.
The platform trust rating of <strong>{data.overallScore}/100</strong> indicates a{' '}
<span className="text-indigo-400 font-semibold">{data.riskLevel} risk</span> of regulatory non-compliance with the Consumer Financial Protection Bureau (CFPB) guidelines.
</>
) : (
<>
This interface is clean! The NLP model scanned all textual and layout layers and did not detect any
cognitive friction, forced continuity, hidden costs, or urgency tactics. The trust score of{' '}
<strong>{data.overallScore}/100</strong> indicates alignment with CFPB consumer protection policies.
</>
)}
</p>
</div>
</div>
</div>
);
}