Spaces:
Sleeping
Sleeping
| 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> | |
| ); | |
| } |