import React, { useState, useCallback } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Upload, File, X, Shield, AlertCircle, CheckCircle, Hash } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Card } from '@/components/ui/card'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { useToast } from '@/hooks/use-toast'; import { cn } from '@/lib/utils'; interface EvidenceFile { id: string; file: File; status: 'uploading' | 'hashing' | 'binding' | 'complete' | 'error'; hash?: string; artifactId?: string; progress: number; error?: string; } interface EvidenceUploadProps { caseId: string; onUploadComplete?: (artifactId: string) => void; } export function EvidenceUpload({ caseId, onUploadComplete }: EvidenceUploadProps) { const [files, setFiles] = useState([]); const [dragActive, setDragActive] = useState(false); const [evidenceType, setEvidenceType] = useState(''); const [description, setDescription] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const { toast } = useToast(); const allowedTypes = ['application/pdf', 'image/jpeg', 'image/png', 'image/gif', 'video/mp4', 'text/plain']; const maxFileSize = 100 * 1024 * 1024; // 100MB const validateFile = (file: File): string | null => { if (!allowedTypes.includes(file.type)) { return `File type ${file.type} not allowed`; } if (file.size > maxFileSize) { return 'File size exceeds 100MB limit'; } return null; }; const handleDrag = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); if (e.type === "dragenter" || e.type === "dragover") { setDragActive(true); } else if (e.type === "dragleave") { setDragActive(false); } }, []); const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setDragActive(false); const droppedFiles = Array.from(e.dataTransfer.files); processFiles(droppedFiles); }, []); const processFiles = (fileList: File[]) => { const newFiles: EvidenceFile[] = []; fileList.forEach(file => { const error = validateFile(file); if (error) { toast({ title: "File Validation Error", description: `${file.name}: ${error}`, variant: "destructive", }); return; } const evidenceFile: EvidenceFile = { id: Math.random().toString(36).substr(2, 9), file, status: 'uploading', progress: 0, }; newFiles.push(evidenceFile); }); setFiles(prev => [...prev, ...newFiles]); // Start processing each file newFiles.forEach(processFile); }; const processFile = async (evidenceFile: EvidenceFile) => { try { // Step 1: Hash the file updateFileStatus(evidenceFile.id, 'hashing', 25); const hash = await calculateFileHash(evidenceFile.file); // Step 2: Upload to IPFS updateFileStatus(evidenceFile.id, 'uploading', 50); const formData = new FormData(); formData.append('file', evidenceFile.file); formData.append('hash', hash); const uploadResponse = await fetch('/api/v1/evidence/upload', { method: 'POST', headers: { 'Authorization': `Bearer ${localStorage.getItem('chittychain_token')}`, }, body: formData, }); if (!uploadResponse.ok) { throw new Error('Upload failed'); } const uploadData = await uploadResponse.json(); // Step 3: Bind to blockchain updateFileStatus(evidenceFile.id, 'binding', 75); const bindResponse = await fetch('/api/v1/artifacts/bind', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('chittychain_token')}`, }, body: JSON.stringify({ case_id: caseId, evidence_type: evidenceType || 'document', description: description || evidenceFile.file.name, file_hash: hash, ipfs_hash: uploadData.ipfsHash, metadata: { filename: evidenceFile.file.name, fileSize: evidenceFile.file.size, mimeType: evidenceFile.file.type, uploadedAt: new Date().toISOString(), }, }), }); if (!bindResponse.ok) { throw new Error('Blockchain binding failed'); } const bindData = await bindResponse.json(); // Complete setFiles(prev => prev.map(f => f.id === evidenceFile.id ? { ...f, status: 'complete', progress: 100, hash, artifactId: bindData.artifact_id } : f )); toast({ title: "Evidence Uploaded Successfully", description: `Artifact ID: ${bindData.artifact_id}`, }); onUploadComplete?.(bindData.artifact_id); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; setFiles(prev => prev.map(f => f.id === evidenceFile.id ? { ...f, status: 'error', error: errorMessage } : f )); toast({ title: "Upload Failed", description: `${evidenceFile.file.name}: ${errorMessage}`, variant: "destructive", }); } }; const updateFileStatus = (id: string, status: EvidenceFile['status'], progress: number) => { setFiles(prev => prev.map(f => f.id === id ? { ...f, status, progress } : f )); }; const calculateFileHash = async (file: File): Promise => { const arrayBuffer = await file.arrayBuffer(); const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer); const hashArray = Array.from(new Uint8Array(hashBuffer)); return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); }; const removeFile = (id: string) => { setFiles(prev => prev.filter(f => f.id !== id)); }; const getStatusIcon = (status: EvidenceFile['status']) => { switch (status) { case 'uploading': case 'hashing': case 'binding': return
; case 'complete': return ; case 'error': return ; } }; const getStatusText = (status: EvidenceFile['status']) => { switch (status) { case 'uploading': return 'Uploading to IPFS...'; case 'hashing': return 'Calculating hash...'; case 'binding': return 'Binding to blockchain...'; case 'complete': return 'Evidence secured'; case 'error': return 'Upload failed'; } }; return (

Submit Evidence

Upload legal documents with cryptographic verification and blockchain binding

{/* Evidence Metadata Form */}