import { useState, useCallback, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useDropzone } from 'react-dropzone'; import { toast } from 'react-toastify'; // Página pública: NÃO requer autenticação do usuário export function ClientProvaUpload() { const [file, setFile] = useState(null); const [uploading, setUploading] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const [error, setError] = useState(null); const [userLocation, setUserLocation] = useState<{ latitude: number; longitude: number } | null>(null); const [locationPermission, setLocationPermission] = useState('pending'); const [filePreview, setFilePreview] = useState(null); const navigate = useNavigate(); // Tentar obter a localização do usuário quando o componente montar useEffect(() => { if (navigator.geolocation) { setLocationPermission('requesting'); navigator.geolocation.getCurrentPosition( (position) => { console.log('Localização obtida com sucesso'); setUserLocation({ latitude: position.coords.latitude, longitude: position.coords.longitude }); setLocationPermission('granted'); }, (error) => { console.log('Erro ao obter localização:', error.message); setLocationPermission('denied'); }, { enableHighAccuracy: true, timeout: 5000, maximumAge: 0 } ); } else { console.log('Geolocalização não suportada pelo navegador'); setLocationPermission('unsupported'); } }, []); // Validação extra do arquivo antes do upload function validateFile(file: File | null): string | null { if (!file) return 'Nenhum arquivo selecionado.'; const allowedTypes = [ 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'application/pdf' ]; if (!allowedTypes.includes(file.type)) { return 'Tipo de arquivo não suportado. Use JPG, PNG, GIF, WEBP ou PDF.'; } if (file.size > 10 * 1024 * 1024) { return 'Arquivo excede o tamanho máximo de 10MB.'; } return null; } const onDrop = useCallback(async (acceptedFiles: File[]) => { if (acceptedFiles.length > 0) { const selectedFile = acceptedFiles[0]; const validationError = validateFile(selectedFile); if (validationError) { toast.error(validationError); setFile(null); setFilePreview(null); return; } setFile(selectedFile); // Criar preview para imagens if (selectedFile.type.startsWith('image/')) { const reader = new FileReader(); reader.onload = () => { setFilePreview(reader.result as string); }; reader.readAsDataURL(selectedFile); } else { setFilePreview(null); } } }, []); const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept: { 'image/*': ['.jpg', '.jpeg', '.png', '.gif', '.webp'], 'application/pdf': ['.pdf'] }, multiple: false, maxSize: 10 * 1024 * 1024 // 10MB }); const handleSubmit = async () => { try { if (!file) { toast.error('Por favor, selecione um arquivo primeiro'); return; } // Validação extra antes do envio const validationError = validateFile(file); if (validationError) { toast.error(validationError); return; } setUploading(true); setUploadProgress(0); setError(null); const formData = new FormData(); // Adicionar arquivo com todas as informações preservadas formData.append('files', file); formData.append('modification_times', String(file.lastModified)); formData.append('file_names', file.name); formData.append('file_sizes', String(file.size)); formData.append('file_types', file.type); // Adicionar metadados de contexto formData.append('user_agent', navigator.userAgent); formData.append('language', navigator.language); formData.append('viewport_width', String(window.innerWidth)); formData.append('viewport_height', String(window.innerHeight)); formData.append('referrer', document.referrer); formData.append('timezone', Intl.DateTimeFormat().resolvedOptions().timeZone); formData.append('timezone_offset', String(new Date().getTimezoneOffset())); formData.append('platform', navigator.platform); formData.append('cookies_enabled', String(navigator.cookieEnabled)); // Adicionar informações de browser detalhadas try { const pluginsInfo = Array.from(navigator.plugins).map(p => p.name).join(','); formData.append('browser_plugins', pluginsInfo); } catch (e) { formData.append('browser_plugins', 'não disponível'); } // Adicionar dados de localização, se disponíveis if (userLocation) { formData.append('latitude', String(userLocation.latitude)); formData.append('longitude', String(userLocation.longitude)); } formData.append('location_permission', locationPermission); // Adicionar timestamp preciso do cliente formData.append('client_timestamp', new Date().toISOString()); // Simulação de progresso const progressInterval = setInterval(() => { setUploadProgress(prev => { if (prev >= 95) { clearInterval(progressInterval); return prev; } return prev + 5; }); }, 300); // Upload sem autenticação (página pública) let response: Response; try { response = await fetch('/api/provas/upload', { method: 'POST', body: formData, }); } catch (networkError) { clearInterval(progressInterval); setUploadProgress(0); throw new Error('Erro de conexão. Verifique sua internet e tente novamente.'); } clearInterval(progressInterval); setUploadProgress(100); // Verificar se a resposta foi bem-sucedida if (!response.ok) { const errorText = await response.text(); let errorMessage; try { const errorJson = JSON.parse(errorText); errorMessage = errorJson.error || errorJson.details || 'Erro ao enviar arquivo'; } catch { errorMessage = errorText || 'Erro ao enviar arquivo'; } throw new Error(errorMessage); } // Ler o corpo da resposta let result: any; try { result = await response.json(); } catch { throw new Error('Resposta inesperada do servidor.'); } if (result && result.id) { toast.success('Arquivo enviado com sucesso!'); // Navegar para a página de status navigate(`/public/provas/status/${result.id}`); } else { throw new Error('ID do documento não encontrado na resposta'); } } catch (error: any) { console.error('Erro detalhado no upload:', error); setError(error.message || 'Erro ao enviar arquivo. Por favor, tente novamente.'); toast.error('Falha no upload: ' + (error.message || 'Erro desconhecido')); } finally { setUploading(false); } }; return (

Validação Digital de Documentos

Valide e proteja a autenticidade de seus documentos e imagens com timestamping seguro

Upload de Documento para Validação

Formatos aceitos: JPG, PNG, GIF, PDF (máx. 10MB)

{filePreview ? (
Preview

{file?.name} ({(file?.size || 0) / 1024 < 1024 ? `${Math.round((file?.size || 0) / 1024)} KB` : `${Math.round((file?.size || 0) / 1024 / 1024 * 10) / 10} MB`})

) : (

{isDragActive ? "Solte o arquivo aqui..." : "Arraste e solte um arquivo aqui, ou clique para selecionar"}

Os metadados originais e informações de contexto serão preservados para melhor validação

)}
{locationPermission === 'granted' && (
✓ Localização obtida: Esta informação ajudará na validação do documento
)} {locationPermission === 'denied' && (
⚠️ Localização não permitida: A validação ainda funcionará, mas sem dados de geolocalização
)} {error && (
Erro: {error}
)} {uploading && (
Enviando arquivo
{uploadProgress}%
)}
Nota sobre privacidade: Este serviço coleta metadados como informações do dispositivo, localização e timestamp para garantir a validação adequada do documento. Todos os dados são criptografados e armazenados com segurança.
); }