fintechdarkpatterns / src /app /components /DarkPatternsList.tsx
yujisium's picture
Add model evaluation, explainability, text lab, and working demo samples
11b9a48 verified
Raw
History Blame
8.48 kB
import { DarkPattern } from '../types/analysis';
import { Badge } from './ui/badge';
import { AlertTriangle, Brain, FileText, Lightbulb } from 'lucide-react';
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from './ui/accordion';
interface DarkPatternsListProps {
patterns: DarkPattern[];
}
export function DarkPatternsList({ patterns }: DarkPatternsListProps) {
const getSeverityBadgeClass = (severity: string) => {
switch (severity) {
case 'critical':
return 'bg-rose-500/10 text-rose-400 border border-rose-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';
default:
return 'bg-indigo-500/10 text-indigo-400 border border-indigo-500/20';
}
};
const getSeverityBorderColor = (severity: string) => {
switch (severity) {
case 'critical':
return 'border-rose-500/25 bg-rose-500/5 hover:border-rose-500/40';
case 'high':
return 'border-orange-500/25 bg-orange-500/5 hover:border-orange-500/40';
case 'medium':
return 'border-amber-500/25 bg-amber-500/5 hover:border-amber-500/40';
default:
return 'border-indigo-500/25 bg-indigo-500/5 hover:border-indigo-500/40';
}
};
// Sort by severity
const sortedPatterns = [...patterns].sort((a, b) => {
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
return severityOrder[a.severity] - severityOrder[b.severity];
});
return (
<div className="space-y-6">
{/* Summary */}
<div className="bg-slate-900/30 rounded-xl p-4 border border-border">
<div className="flex items-center gap-3">
<AlertTriangle className="w-5 h-5 text-indigo-400" />
<p className="text-xs text-slate-300">
Identified <strong>{patterns.length} deceptive copy triggers</strong> in the layout layers.
Expand each item below for compliance details and XAI fixes.
</p>
</div>
</div>
{/* Patterns List */}
<Accordion type="single" collapsible className="space-y-4">
{sortedPatterns.map((pattern) => (
<AccordionItem
key={pattern.id}
value={pattern.id}
className={`border rounded-xl overflow-hidden transition-all duration-300 ${getSeverityBorderColor(pattern.severity)}`}
>
<AccordionTrigger className="px-6 py-4 hover:no-underline text-slate-100">
<div className="flex items-center gap-4 w-full text-left">
<Badge className={`${getSeverityBadgeClass(pattern.severity)} text-[10px] font-bold px-2 py-0.5 rounded-md`}>
{pattern.severity}
</Badge>
<div className="flex-1 min-w-0">
<div className="font-bold text-xs sm:text-sm text-slate-200">{pattern.type}</div>
<div className="text-[11px] text-slate-400 truncate mt-0.5">{pattern.description}</div>
</div>
<div className="text-[10px] font-mono text-slate-400 shrink-0 bg-slate-950/20 px-2 py-1 rounded border border-white/5">
{pattern.confidence}% confidence
</div>
</div>
</AccordionTrigger>
<AccordionContent className="px-6 pb-6">
<div className="space-y-4 pt-4 border-t border-white/5">
{/* Detected copy + XAI phrases */}
{pattern.evidence && (
<div className="bg-slate-950/30 rounded-xl p-4 border border-indigo-500/15">
<h5 className="font-bold text-xs text-indigo-300 mb-2">Detected Copy</h5>
<p className="text-xs text-slate-200 font-mono leading-relaxed">"{pattern.evidence}"</p>
{pattern.explanation && pattern.explanation.length > 0 && (
<div className="flex flex-wrap gap-1.5 mt-3">
{pattern.explanation.map((e) => (
<span
key={e.phrase}
className="text-[10px] font-mono px-2 py-0.5 rounded bg-indigo-500/15 text-indigo-300 border border-indigo-500/25"
title={`contribution weight ${e.weight}`}
>
{e.phrase}
</span>
))}
</div>
)}
</div>
)}
{/* Description */}
<div className="bg-slate-950/30 rounded-xl p-4 border border-white/5">
<div className="flex items-start gap-3">
<FileText className="w-4 h-4 text-indigo-400 shrink-0 mt-0.5" />
<div>
<h5 className="font-bold text-xs text-slate-200 mb-1">Issue Description</h5>
<p className="text-xs text-slate-300">{pattern.description}</p>
</div>
</div>
</div>
{/* CFPB Violation */}
<div className="bg-slate-950/30 rounded-xl p-4 border border-rose-500/15">
<div className="flex items-start gap-3">
<AlertTriangle className="w-4 h-4 text-rose-400 shrink-0 mt-0.5" />
<div>
<h5 className="font-bold text-xs text-rose-300 mb-1">CFPB Compliance Conflict</h5>
<p className="text-xs text-rose-200/80 leading-relaxed">{pattern.cfpbViolation}</p>
</div>
</div>
</div>
{/* XAI Recommendation */}
<div className="bg-slate-950/30 rounded-xl p-4 border border-emerald-500/15">
<div className="flex items-start gap-3">
<Lightbulb className="w-4 h-4 text-emerald-400 shrink-0 mt-0.5" />
<div>
<h5 className="font-bold text-xs text-emerald-300 mb-1">Recommended Fix</h5>
<p className="text-xs text-emerald-200/80 leading-relaxed">{pattern.recommendation}</p>
</div>
</div>
</div>
{/* Location Info */}
<div className="bg-slate-950/30 rounded-xl p-4 border border-white/5">
<div className="flex items-start gap-3">
<Brain className="w-4 h-4 text-purple-400 shrink-0 mt-0.5" />
<div className="flex-1">
<h5 className="font-bold text-xs text-slate-200 mb-2">Neural Network Activation Layer</h5>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<span className="text-slate-400">OCR Bounding Box:</span>{' '}
<span className="text-slate-200 font-mono font-medium">
({pattern.location.x}%, {pattern.location.y}%)
</span>
</div>
<div>
<span className="text-slate-400">Visual Box Size:</span>{' '}
<span className="text-slate-200 font-mono font-medium">
{pattern.location.width}% × {pattern.location.height}%
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</AccordionContent>
</AccordionItem>
))}
</Accordion>
{/* About XAI */}
<div className="bg-indigo-500/5 border border-indigo-500/10 rounded-xl p-4">
<h4 className="font-bold text-xs text-indigo-300 mb-2">🤖 Neural Network Audit Mechanism</h4>
<p className="text-xs text-slate-400 leading-relaxed">
The models run OCR to extract text boundaries and classify the linguistic copy structure.
All recommendations are generated by parsing structural cues against TILA, EFTA, and CFPB administrative records.
</p>
</div>
</div>
);
}