All checks were successful
Build & Deploy Frontend / build-push-deploy (push) Successful in 1m45s
115 lines
3.4 KiB
TypeScript
115 lines
3.4 KiB
TypeScript
import React, { useCallback, useState } from 'react';
|
|
import { useDropzone } from 'react-dropzone';
|
|
import { Upload, X, FileText } from 'lucide-react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
interface FileUploadProps {
|
|
onFileSelect: (file: File) => void;
|
|
onFileRemove: () => void;
|
|
selectedFile?: File;
|
|
currentFileUrl?: string;
|
|
accept?: string;
|
|
maxSize?: number;
|
|
className?: string;
|
|
}
|
|
|
|
export function FileUpload({
|
|
onFileSelect,
|
|
onFileRemove,
|
|
selectedFile,
|
|
currentFileUrl,
|
|
accept = '.pdf,.jpg,.jpeg,.png',
|
|
maxSize = 5 * 1024 * 1024, // 5MB
|
|
className
|
|
}: FileUploadProps) {
|
|
const [error, setError] = useState<string>('');
|
|
|
|
const onDrop = useCallback((acceptedFiles: File[], rejectedFiles: any[]) => {
|
|
setError('');
|
|
|
|
if (rejectedFiles.length > 0) {
|
|
const rejection = rejectedFiles[0];
|
|
if (rejection.errors[0]?.code === 'file-too-large') {
|
|
setError('File is too large. Maximum size is 5MB.');
|
|
} else if (rejection.errors[0]?.code === 'file-invalid-type') {
|
|
setError('Invalid file type. Please upload PDF, JPG, or PNG files.');
|
|
} else {
|
|
setError('File upload failed. Please try again.');
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (acceptedFiles.length > 0) {
|
|
onFileSelect(acceptedFiles[0]);
|
|
}
|
|
}, [onFileSelect]);
|
|
|
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
|
onDrop,
|
|
accept: {
|
|
'application/pdf': ['.pdf'],
|
|
'image/jpeg': ['.jpg', '.jpeg'],
|
|
'image/png': ['.png']
|
|
},
|
|
maxSize,
|
|
multiple: false
|
|
});
|
|
|
|
const hasFile = selectedFile || currentFileUrl;
|
|
|
|
return (
|
|
<div className={cn('space-y-2', className)}>
|
|
{!hasFile ? (
|
|
<div
|
|
{...getRootProps()}
|
|
className={cn(
|
|
'border-2 border-dashed rounded-lg p-6 text-center cursor-pointer transition-colors',
|
|
isDragActive
|
|
? 'border-primary bg-primary/5'
|
|
: 'border-muted-foreground/25 hover:border-primary hover:bg-primary/5'
|
|
)}
|
|
>
|
|
<input {...getInputProps()} />
|
|
<Upload className="mx-auto h-8 w-8 text-muted-foreground mb-2" />
|
|
<p className="text-sm text-muted-foreground">
|
|
{isDragActive ? (
|
|
'Drop the file here...'
|
|
) : (
|
|
<>
|
|
Drag & drop an invoice file here, or{' '}
|
|
<span className="text-primary font-medium">click to browse</span>
|
|
</>
|
|
)}
|
|
</p>
|
|
<p className="text-xs text-muted-foreground mt-1">
|
|
PDF, JPG, PNG up to 5MB
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<div className="border rounded-lg p-4 bg-muted/50">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center space-x-2">
|
|
<FileText className="h-4 w-4 text-muted-foreground" />
|
|
<span className="text-sm font-medium">
|
|
{selectedFile ? selectedFile.name : 'Current invoice file'}
|
|
</span>
|
|
</div>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={onFileRemove}
|
|
>
|
|
<X className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{error && (
|
|
<p className="text-sm text-red-600">{error}</p>
|
|
)}
|
|
</div>
|
|
);
|
|
} |