NeahNew/components/missions/file-upload.tsx
2025-05-06 10:51:17 +02:00

288 lines
9.0 KiB
TypeScript

"use client";
import React, { useState, useRef } from 'react';
import { UploadCloud, X, Check, Loader2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { useSession } from 'next-auth/react';
import { toast } from '@/components/ui/use-toast';
interface FileUploadProps {
type: 'logo' | 'attachment';
missionId: string;
onUploadComplete?: (data: any) => void;
maxSize?: number; // in bytes, default 5MB
acceptedFileTypes?: string;
}
export function FileUpload({
type,
missionId,
onUploadComplete,
maxSize = 5 * 1024 * 1024, // 5MB
acceptedFileTypes = type === 'logo' ? 'image/*' : '.pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png'
}: FileUploadProps) {
const { data: session } = useSession();
const [isDragging, setIsDragging] = useState(false);
const [isUploading, setIsUploading] = useState(false);
const [progress, setProgress] = useState(0);
const [file, setFile] = useState<File | null>(null);
const [error, setError] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
// Handle drag events
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(true);
};
const handleDragLeave = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
};
const validateFile = (file: File): boolean => {
// Check file size
if (file.size > maxSize) {
setError(`File size exceeds the limit of ${maxSize / (1024 * 1024)}MB`);
return false;
}
// Check file type for logo
if (type === 'logo' && !file.type.startsWith('image/')) {
setError('Only image files are allowed for logo');
return false;
}
// For attachments, check file extension
if (type === 'attachment') {
const ext = file.name.split('.').pop()?.toLowerCase();
const allowedExt = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'jpg', 'jpeg', 'png'];
if (ext && !allowedExt.includes(ext)) {
setError(`File type .${ext} is not allowed. Allowed types: ${allowedExt.join(', ')}`);
return false;
}
}
setError(null);
return true;
};
const handleFileDrop = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
const droppedFile = e.dataTransfer.files[0];
if (validateFile(droppedFile)) {
setFile(droppedFile);
}
}
};
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files.length > 0) {
const selectedFile = e.target.files[0];
if (validateFile(selectedFile)) {
setFile(selectedFile);
}
}
};
const handleUpload = async () => {
if (!file || !session?.user?.id || !missionId) {
console.error('Upload failed: Missing required data', {
fileExists: !!file,
userIdExists: !!session?.user?.id,
missionIdExists: !!missionId
});
return;
}
console.log('Starting upload process...', {
fileName: file.name,
fileSize: file.size,
fileType: file.type,
missionId,
userId: session.user.id,
uploadType: type
});
setIsUploading(true);
setProgress(0);
try {
// Create form data
const formData = new FormData();
formData.append('file', file);
formData.append('missionId', missionId);
formData.append('type', type);
console.log('FormData prepared, sending to API...');
// Upload the file
const response = await fetch('/api/missions/upload', {
method: 'POST',
body: formData
});
console.log('API response received:', {
status: response.status,
statusText: response.statusText,
ok: response.ok
});
if (!response.ok) {
const errorData = await response.json();
console.error('API returned error:', errorData);
throw new Error(errorData.error || 'Upload failed');
}
const result = await response.json();
console.log('Upload successful, result:', result);
setProgress(100);
// Reset file after successful upload
setTimeout(() => {
setFile(null);
setIsUploading(false);
setProgress(0);
// Call the callback if provided
if (onUploadComplete) {
onUploadComplete(result);
}
toast({
title: 'File uploaded successfully',
description: type === 'logo' ? 'Logo has been updated' : `${file.name} has been added to attachments`,
variant: 'default',
});
}, 1000);
} catch (error) {
console.error('Upload error details:', error);
setIsUploading(false);
toast({
title: 'Upload failed',
description: error instanceof Error ? error.message : 'An error occurred during upload',
variant: 'destructive',
});
}
};
const handleCancel = () => {
setFile(null);
setError(null);
};
return (
<div className="w-full">
{!file ? (
<div
className={`border-2 border-dashed rounded-md p-6 text-center transition-colors ${
isDragging ? 'border-blue-500 bg-blue-50' : 'border-gray-300 bg-gray-50'
}`}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleFileDrop}
>
<div className="flex flex-col items-center justify-center">
<UploadCloud className="h-10 w-10 text-gray-400 mb-2" />
<p className="text-sm mb-2 font-medium text-gray-700">
{type === 'logo' ? 'Upload logo image' : 'Upload attachment'}
</p>
<p className="text-xs text-gray-500 mb-4">
Drag and drop or click to browse
</p>
<Button
variant="outline"
size="sm"
className="bg-white text-gray-700 border-gray-300 hover:bg-gray-50"
onClick={() => fileInputRef.current?.click()}
>
Browse Files
</Button>
<input
type="file"
ref={fileInputRef}
className="hidden"
onChange={handleFileChange}
accept={acceptedFileTypes}
/>
{error && (
<p className="text-xs text-red-500 mt-2">{error}</p>
)}
</div>
</div>
) : (
<div className="border rounded-md p-4 bg-white">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="flex-shrink-0 h-10 w-10 bg-gray-100 rounded-md flex items-center justify-center">
{type === 'logo' ? (
<img
src={URL.createObjectURL(file)}
alt="Preview"
className="h-full w-full object-cover rounded-md"
/>
) : (
<div className="text-xs font-bold bg-blue-100 text-blue-600 h-full w-full rounded-md flex items-center justify-center">
{file.name.split('.').pop()?.toUpperCase()}
</div>
)}
</div>
<div className="min-w-0 flex-1">
<p className="text-sm font-medium text-gray-900 truncate">
{file.name}
</p>
<p className="text-xs text-gray-500">
{(file.size / 1024).toFixed(2)} KB
</p>
</div>
</div>
<div className="flex items-center space-x-2">
{isUploading ? (
<div className="flex items-center">
<Loader2 className="animate-spin h-4 w-4 mr-1 text-blue-500" />
<span className="text-xs text-gray-500">{progress}%</span>
</div>
) : (
<>
<Button
variant="ghost"
size="sm"
className="text-red-500 hover:text-red-700 hover:bg-red-50"
onClick={handleCancel}
>
<X className="h-4 w-4" />
</Button>
<Button
variant="default"
size="sm"
className="bg-blue-600 hover:bg-blue-700 text-white"
onClick={handleUpload}
>
<Check className="h-4 w-4 mr-1" />
Upload
</Button>
</>
)}
</div>
</div>
{isUploading && (
<div className="w-full bg-gray-200 rounded-full h-1.5 mt-3">
<div
className="bg-blue-600 h-1.5 rounded-full"
style={{ width: `${progress}%` }}
></div>
</div>
)}
</div>
)}
</div>
);
}