127 lines
3.6 KiB
TypeScript
127 lines
3.6 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useCallback } from 'react';
|
|
import { supabase } from '@/lib/supabase';
|
|
|
|
interface FileUploadProps {
|
|
bucket: string;
|
|
folder?: string;
|
|
accept?: string;
|
|
maxSize?: number; // in bytes
|
|
onUpload: (url: string, path: string) => void;
|
|
onError?: (error: Error) => void;
|
|
}
|
|
|
|
export function FileUpload({
|
|
bucket,
|
|
folder = 'uploads',
|
|
accept = 'image/*',
|
|
maxSize = 5 * 1024 * 1024, // 5MB default
|
|
onUpload,
|
|
onError,
|
|
}: FileUploadProps) {
|
|
const [uploading, setUploading] = useState(false);
|
|
const [progress, setProgress] = useState(0);
|
|
const [dragOver, setDragOver] = useState(false);
|
|
|
|
const uploadFile = useCallback(
|
|
async (file: File) => {
|
|
// Validierung
|
|
if (file.size > maxSize) {
|
|
onError?.(new Error(`Datei zu gross. Maximum: ${maxSize / 1024 / 1024}MB`));
|
|
return;
|
|
}
|
|
|
|
setUploading(true);
|
|
setProgress(0);
|
|
|
|
try {
|
|
// Unique filename generieren
|
|
const fileExt = file.name.split('.').pop();
|
|
const fileName = `${Date.now()}-${Math.random().toString(36).substring(2)}.${fileExt}`;
|
|
const filePath = `${folder}/${fileName}`;
|
|
|
|
// Upload
|
|
const { error: uploadError } = await supabase.storage
|
|
.from(bucket)
|
|
.upload(filePath, file, {
|
|
cacheControl: '3600',
|
|
upsert: false,
|
|
});
|
|
|
|
if (uploadError) throw uploadError;
|
|
|
|
// URL holen
|
|
const { data } = supabase.storage.from(bucket).getPublicUrl(filePath);
|
|
|
|
onUpload(data.publicUrl, filePath);
|
|
setProgress(100);
|
|
} catch (error) {
|
|
console.error('Upload error:', error);
|
|
onError?.(error instanceof Error ? error : new Error('Upload fehlgeschlagen'));
|
|
} finally {
|
|
setUploading(false);
|
|
}
|
|
},
|
|
[bucket, folder, maxSize, onUpload, onError]
|
|
);
|
|
|
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0];
|
|
if (file) uploadFile(file);
|
|
};
|
|
|
|
const handleDrop = (e: React.DragEvent) => {
|
|
e.preventDefault();
|
|
setDragOver(false);
|
|
const file = e.dataTransfer.files?.[0];
|
|
if (file) uploadFile(file);
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={`
|
|
relative border-2 border-dashed rounded-lg p-6 text-center
|
|
transition-colors cursor-pointer
|
|
${dragOver ? 'border-primary bg-primary/5' : 'border-muted-foreground/25'}
|
|
${uploading ? 'pointer-events-none opacity-50' : 'hover:border-primary'}
|
|
`}
|
|
onDragOver={(e) => {
|
|
e.preventDefault();
|
|
setDragOver(true);
|
|
}}
|
|
onDragLeave={() => setDragOver(false)}
|
|
onDrop={handleDrop}
|
|
>
|
|
<input
|
|
type="file"
|
|
accept={accept}
|
|
onChange={handleChange}
|
|
disabled={uploading}
|
|
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
|
|
/>
|
|
|
|
{uploading ? (
|
|
<div className="space-y-2">
|
|
<div className="w-full bg-muted rounded-full h-2">
|
|
<div
|
|
className="bg-primary h-2 rounded-full transition-all"
|
|
style={{ width: `${progress}%` }}
|
|
/>
|
|
</div>
|
|
<p className="text-sm text-muted-foreground">Uploading...</p>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-2">
|
|
<p className="text-sm font-medium">
|
|
Datei hierher ziehen oder klicken zum Auswaehlen
|
|
</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
Max. {maxSize / 1024 / 1024}MB
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|