Numan Saeed
Upgrade to React + FastAPI (Docker-based)
a874986
import { useCallback } from 'react';
import { useDropzone } from 'react-dropzone';
import { Upload, FileImage, FileText, Loader2 } from 'lucide-react';
import { cn } from '../lib/utils';
import { isDicomFile } from '../lib/api';
interface FileUploadProps {
onUpload: (file: File) => void;
preview: string | null;
currentFile: File | null;
isLoading?: boolean;
}
export function FileUpload({ onUpload, preview, currentFile, isLoading = false }: FileUploadProps) {
const onDrop = useCallback(
(acceptedFiles: File[]) => {
if (acceptedFiles.length > 0) {
onUpload(acceptedFiles[0]);
}
},
[onUpload]
);
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
accept: {
'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp'],
'application/dicom': ['.dcm', '.dicom'],
'application/octet-stream': ['.dcm', '.dicom'],
},
maxFiles: 1,
});
const isDicom = currentFile ? isDicomFile(currentFile.name) : false;
return (
<div className="h-full w-full flex flex-col">
<div
{...getRootProps()}
className={cn(
'flex-1 relative border-2 border-dashed rounded-xl transition-all duration-200 cursor-pointer overflow-hidden',
'hover:border-nvidia-green hover:bg-nvidia-green/5',
isDragActive
? 'border-nvidia-green bg-nvidia-green/10'
: 'border-dark-border bg-dark-input'
)}
>
<input {...getInputProps()} />
{isLoading ? (
// Loading state for DICOM preview
<div className="absolute inset-0 flex flex-col items-center justify-center gap-3 bg-slate-900">
<Loader2 className="w-8 h-8 text-nvidia-green animate-spin" />
<p className="text-white text-sm">Loading DICOM preview...</p>
</div>
) : preview ? (
// Show preview image - dark background for medical images
<div className="absolute inset-0 flex items-center justify-center bg-slate-900 rounded-lg">
<img
src={preview}
alt="Preview"
className="max-w-full max-h-full w-full h-full object-contain"
/>
<div className="absolute inset-0 bg-black/60 opacity-0 hover:opacity-100 transition-opacity flex items-center justify-center rounded-lg">
<p className="text-white text-sm font-medium">Click or drop to replace</p>
</div>
{/* File type badge */}
<div className={cn(
'absolute top-3 right-3 px-2.5 py-1 rounded-full text-xs font-semibold shadow-lg',
isDicom ? 'bg-nvidia-green text-white' : 'bg-accent-blue text-white'
)}>
{isDicom ? 'DICOM' : 'IMAGE'}
</div>
</div>
) : (
// Empty state / upload prompt
<div className="absolute inset-0 flex flex-col items-center justify-center gap-4 p-4">
<div className="p-4 rounded-full bg-white border border-dark-border shadow-card">
<Upload className="w-8 h-8 text-text-muted" />
</div>
<div className="text-center">
<p className="text-text-primary font-medium text-sm mb-1">
{isDragActive ? 'Drop file here' : 'Drop or click to upload'}
</p>
<p className="text-text-muted text-xs">
Supports DICOM and image files
</p>
</div>
{/* Format hints */}
<div className="flex gap-3 mt-2">
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-nvidia-green/10 border border-nvidia-green/30">
<FileText className="w-3.5 h-3.5 text-nvidia-green" />
<span className="text-xs font-medium text-nvidia-green">DICOM</span>
<span className="text-[10px] px-1.5 py-0.5 rounded-full bg-nvidia-green text-white">Best</span>
</div>
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-accent-blue/10 border border-accent-blue/30">
<FileImage className="w-3.5 h-3.5 text-accent-blue" />
<span className="text-xs font-medium text-accent-blue">PNG/JPEG</span>
</div>
</div>
</div>
)}
</div>
</div>
);
}