fintechdarkpatterns / src /app /components /UploadSection.tsx
yujisium's picture
Add model evaluation, explainability, text lab, and working demo samples
11b9a48 verified
Raw
History Blame
8.45 kB
import { useState } from 'react';
import { Upload, Image as ImageIcon, Loader2, Shield } from 'lucide-react';
import { Button } from './ui/button';
import { Card } from './ui/card';
import { API_BASE_URL } from '../config';
interface UploadSectionProps {
onAnalyze: (imageUrl: string) => void;
isAnalyzing: boolean;
}
const SAMPLE_IMAGES = [
{
id: '1',
name: 'Urgency & Scarcity Checkout',
url: `${API_BASE_URL}/api/samples/checkout_urgency.png`,
description: 'Fintech checkout with a countdown timer, fake spot scarcity, live-viewer social proof and a confirmshaming opt-out.'
},
{
id: '2',
name: 'Confirmshaming Cancellation',
url: `${API_BASE_URL}/api/samples/cancel_confirmshaming.png`,
description: 'Subscription cancellation modal using guilt-inducing copy, loss framing and a buried 6-step cancel flow.'
},
{
id: '3',
name: 'Pre-Selected Fees Signup',
url: `${API_BASE_URL}/api/samples/signup_sneaking.png`,
description: 'Loan account signup with pre-checked paid add-ons, a trial that silently converts, and sneaked data sharing.'
}
];
export function UploadSection({ onAnalyze, isAnalyzing }: UploadSectionProps) {
const [selectedSample, setSelectedSample] = useState<string | null>(null);
const [uploadedImage, setUploadedImage] = useState<string | null>(null);
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
const result = e.target?.result as string;
setUploadedImage(result);
setSelectedSample(null);
};
reader.readAsDataURL(file);
}
};
const handleSampleSelect = (url: string) => {
setSelectedSample(url);
setUploadedImage(null);
};
const handleAnalyze = () => {
const imageUrl = uploadedImage || selectedSample;
if (imageUrl) {
onAnalyze(imageUrl);
}
};
const activeImage = uploadedImage || selectedSample;
return (
<div className="space-y-6">
<Card className="p-8 bg-card/40 backdrop-blur-md border border-border shadow-xl rounded-2xl relative overflow-hidden transition-colors duration-300">
{/* Glow overlay */}
<div className="absolute top-0 right-0 w-80 h-80 bg-indigo-500/10 rounded-full blur-3xl -z-10 pointer-events-none" />
<div className="absolute bottom-0 left-0 w-80 h-80 bg-purple-500/5 rounded-full blur-3xl -z-10 pointer-events-none" />
<h2 className="text-xl font-bold text-foreground mb-6 flex items-center gap-2">
<Upload className="w-5 h-5 text-indigo-500 dark:text-indigo-400" />
Audit New UI Interface
</h2>
<div className="grid md:grid-cols-2 gap-8">
{/* Upload Area (Fits target image directly inside) */}
<div className="space-y-4">
<label
htmlFor="file-upload"
className={`relative flex flex-col items-center justify-center w-full h-72 border-2 border-dashed rounded-xl cursor-pointer overflow-hidden transition-all duration-300 ${
isAnalyzing
? 'border-border bg-muted/10 cursor-not-allowed'
: activeImage
? 'border-indigo-500/50 bg-slate-900/10 dark:bg-slate-950/40 shadow-inner'
: 'border-border bg-muted/20 hover:border-indigo-500/50 hover:bg-muted/40'
}`}
>
{activeImage ? (
<>
<img
src={activeImage}
alt="Uploaded UI Target"
className="w-full h-full object-contain p-2"
/>
{/* Hover Overlay */}
<div className="absolute inset-0 bg-slate-950/50 opacity-0 hover:opacity-100 transition-opacity duration-300 flex flex-col items-center justify-center text-center p-4">
<Upload className="w-8 h-8 text-indigo-400 mb-2" />
<span className="text-xs font-extrabold text-white">Click or Drop to Replace Screenshot</span>
</div>
{/* Bottom Indicator badge */}
<div className="absolute bottom-2 left-2">
<span className="text-[9px] font-mono bg-indigo-600 text-white px-2.5 py-1 rounded-md uppercase tracking-wider font-extrabold shadow-lg border border-indigo-500/20">
Target Interface Loaded
</span>
</div>
</>
) : (
<div className="flex flex-col items-center justify-center pt-5 pb-6 px-4 text-center">
<div className="p-4 bg-indigo-500/5 rounded-2xl mb-4 border border-indigo-500/10">
<Upload className="w-10 h-10 text-indigo-500 dark:text-indigo-400 animate-pulse" />
</div>
<p className="mb-2 text-sm text-foreground/80">
<span className="font-semibold text-indigo-500 dark:text-indigo-400">Click to upload</span> or drag and drop
</p>
<p className="text-xs text-muted-foreground">PNG, JPG or GIF (MAX. 10MB)</p>
</div>
)}
<input
id="file-upload"
type="file"
className="hidden"
accept="image/*"
onChange={handleFileUpload}
disabled={isAnalyzing}
/>
</label>
</div>
{/* Sample Images */}
<div className="space-y-4">
<div className="flex items-center gap-2 mb-4">
<ImageIcon className="w-5 h-5 text-indigo-500 dark:text-indigo-400" />
<h3 className="font-semibold text-foreground">Select Fintech Sample Screen</h3>
</div>
<div className="space-y-3">
{SAMPLE_IMAGES.map((sample) => (
<button
key={sample.id}
onClick={() => handleSampleSelect(sample.url)}
disabled={isAnalyzing}
className={`w-full text-left p-4 rounded-xl border transition-all duration-300 relative overflow-hidden group ${
selectedSample === sample.url
? 'border-indigo-500 bg-indigo-500/5 shadow-md shadow-indigo-500/5'
: 'border-border bg-muted/10 hover:border-slate-400 dark:hover:border-slate-700 hover:bg-muted/20'
} ${isAnalyzing ? 'opacity-50 cursor-not-allowed' : ''}`}
>
<div className="font-bold text-sm text-foreground mb-1 group-hover:text-indigo-500 dark:group-hover:text-indigo-400 transition-colors">
{sample.name}
</div>
<div className="text-xs text-muted-foreground line-clamp-2 leading-relaxed">
{sample.description}
</div>
</button>
))}
</div>
</div>
</div>
{/* Analyze Button */}
<div className="mt-8 flex justify-center border-t border-border pt-6">
<Button
onClick={handleAnalyze}
disabled={!activeImage || isAnalyzing}
size="lg"
className={`min-w-[240px] h-12 rounded-xl text-xs font-bold uppercase tracking-wider transition-all duration-300 ${
!activeImage
? 'bg-muted text-muted-foreground cursor-not-allowed border border-border'
: 'bg-gradient-to-r from-indigo-600 via-indigo-500 to-purple-600 hover:from-indigo-500 hover:to-purple-500 text-white shadow-[0_0_20px_rgba(99,102,241,0.3)] hover:scale-[1.03]'
}`}
>
{isAnalyzing ? (
<>
<Loader2 className="w-5 h-5 mr-2 animate-spin text-white" />
Processing AI Auditing...
</>
) : (
<>
<Shield className="w-5 h-5 mr-2 text-white" />
Analyze Deceptive Copywriting
</>
)}
</Button>
</div>
</Card>
</div>
);
}