Extract components and hooks from oversized pages into colocated _components/ and _hooks/ subdirectories to enforce the 500-LOC hard cap. page.tsx files reduced to 205, 121, and 136 LOC respectively. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
101 lines
3.1 KiB
TypeScript
101 lines
3.1 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useCallback } from 'react'
|
|
|
|
export function UploadZone({
|
|
onFilesAdded,
|
|
isDisabled,
|
|
}: {
|
|
onFilesAdded: (files: File[]) => void
|
|
isDisabled: boolean
|
|
}) {
|
|
const [isDragging, setIsDragging] = useState(false)
|
|
|
|
const handleDragOver = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault()
|
|
if (!isDisabled) setIsDragging(true)
|
|
}, [isDisabled])
|
|
|
|
const handleDragLeave = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault()
|
|
setIsDragging(false)
|
|
}, [])
|
|
|
|
const handleDrop = useCallback(
|
|
(e: React.DragEvent) => {
|
|
e.preventDefault()
|
|
setIsDragging(false)
|
|
if (isDisabled) return
|
|
const files = Array.from(e.dataTransfer.files).filter(
|
|
f => f.type === 'application/pdf' || f.type.startsWith('image/')
|
|
)
|
|
if (files.length > 0) onFilesAdded(files)
|
|
},
|
|
[onFilesAdded, isDisabled]
|
|
)
|
|
|
|
const handleFileSelect = useCallback(
|
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
if (e.target.files && !isDisabled) {
|
|
onFilesAdded(Array.from(e.target.files))
|
|
}
|
|
},
|
|
[onFilesAdded, isDisabled]
|
|
)
|
|
|
|
return (
|
|
<div
|
|
onDragOver={handleDragOver}
|
|
onDragLeave={handleDragLeave}
|
|
onDrop={handleDrop}
|
|
className={`relative border-2 border-dashed rounded-xl p-12 text-center transition-all ${
|
|
isDisabled
|
|
? 'border-gray-200 bg-gray-50 cursor-not-allowed'
|
|
: isDragging
|
|
? 'border-purple-500 bg-purple-50'
|
|
: 'border-gray-300 hover:border-purple-400 hover:bg-purple-50/50 cursor-pointer'
|
|
}`}
|
|
>
|
|
<input
|
|
type="file"
|
|
accept=".pdf,image/*"
|
|
multiple
|
|
onChange={handleFileSelect}
|
|
disabled={isDisabled}
|
|
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer disabled:cursor-not-allowed"
|
|
/>
|
|
<div className="flex flex-col items-center gap-4">
|
|
<div className={`w-16 h-16 rounded-full flex items-center justify-center ${isDragging ? 'bg-purple-100' : 'bg-gray-100'}`}>
|
|
<svg
|
|
className={`w-8 h-8 ${isDragging ? 'text-purple-600' : 'text-gray-400'}`}
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<p className="text-lg font-medium text-gray-900">
|
|
{isDragging ? 'Dateien hier ablegen' : 'Dokumente hochladen'}
|
|
</p>
|
|
<p className="mt-1 text-sm text-gray-500">
|
|
Ziehen Sie PDF-Dateien hierher oder klicken Sie zum Auswaehlen
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center gap-2 text-xs text-gray-400">
|
|
<span>Unterstuetzte Formate:</span>
|
|
<span className="px-2 py-0.5 bg-gray-100 rounded">PDF</span>
|
|
<span className="px-2 py-0.5 bg-gray-100 rounded">JPG</span>
|
|
<span className="px-2 py-0.5 bg-gray-100 rounded">PNG</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|