"use client"; import { useState } from "react"; import { Upload, X, FileText, CheckCircle, AlertCircle, Loader2 } from "lucide-react"; import { Button } from "~/components/ui/button"; import { Progress } from "~/components/ui/progress"; import { api } from "~/trpc/react"; import { toast } from "~/components/ui/use-toast"; import { cn } from "~/lib/utils"; import axios from "axios"; interface ConsentUploadFormProps { studyId: string; participantId: string; consentFormId: string; onSuccess: () => void; onCancel: () => void; } export function ConsentUploadForm({ studyId, participantId, consentFormId, onSuccess, onCancel, }: ConsentUploadFormProps) { const [file, setFile] = useState(null); const [isUploading, setIsUploading] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); // Mutations const getUploadUrlMutation = api.participants.getConsentUploadUrl.useMutation(); const recordConsentMutation = api.participants.recordConsent.useMutation(); const handleFileChange = (e: React.ChangeEvent) => { if (e.target.files && e.target.files[0]) { const selectedFile = e.target.files[0]; // Validate size (10MB) if (selectedFile.size > 10 * 1024 * 1024) { toast({ title: "File too large", description: "Maximum file size is 10MB", variant: "destructive", }); return; } // Validate type const allowedTypes = ["application/pdf", "image/png", "image/jpeg", "image/jpg"]; if (!allowedTypes.includes(selectedFile.type)) { toast({ title: "Invalid file type", description: "Please upload a PDF, PNG, or JPG file", variant: "destructive", }); return; } setFile(selectedFile); } }; const handleUpload = async () => { if (!file) return; try { setIsUploading(true); setUploadProgress(0); // 1. Get Presigned URL const { url, key } = await getUploadUrlMutation.mutateAsync({ studyId, participantId, filename: file.name, contentType: file.type, size: file.size, }); // 2. Upload to MinIO await axios.put(url, file, { headers: { "Content-Type": file.type, }, onUploadProgress: (progressEvent) => { if (progressEvent.total) { const percentCompleted = Math.round( (progressEvent.loaded * 100) / progressEvent.total ); setUploadProgress(percentCompleted); } }, }); // 3. Record Consent in DB await recordConsentMutation.mutateAsync({ participantId, consentFormId, storagePath: key, }); toast({ title: "Consent Recorded", description: "The consent form has been uploaded and recorded successfully.", }); onSuccess(); } catch (error) { console.error("Upload failed:", error); toast({ title: "Upload Failed", description: error instanceof Error ? error.message : "An unexpected error occurred", variant: "destructive", }); setIsUploading(false); } }; return (
{!file ? (

Upload Signed Consent

Drag and drop or click to select
PDF, PNG, JPG up to 10MB

) : (

{file.name}

{(file.size / 1024 / 1024).toFixed(2)} MB

{!isUploading && ( )}
{isUploading && (
Uploading... {uploadProgress}%
)}
)}
); }