- useIntakeStore: step tracking, photos (max 3), aiResult, editedName, error - submitIntake(): multipart FormData POST /api/intake with IntakeResponse type - DropZone: react-dropzone with camera capture, volt hover state, slot counter - PhotoPreview: thumbnail grid with X remove button per photo
63 lines
1.8 KiB
TypeScript
63 lines
1.8 KiB
TypeScript
import { useCallback } from 'react'
|
|
import { useDropzone } from 'react-dropzone'
|
|
import { Upload, Camera } from 'lucide-react'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
interface DropZoneProps {
|
|
photoCount: number
|
|
onDrop: (files: File[]) => void
|
|
}
|
|
|
|
const MAX = 3
|
|
|
|
export function DropZone({ photoCount, onDrop }: DropZoneProps) {
|
|
const remaining = MAX - photoCount
|
|
const disabled = remaining === 0
|
|
|
|
const handleDrop = useCallback(
|
|
(accepted: File[]) => {
|
|
onDrop(accepted.slice(0, remaining))
|
|
},
|
|
[onDrop, remaining],
|
|
)
|
|
|
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
|
accept: { 'image/*': [] },
|
|
maxFiles: remaining,
|
|
disabled,
|
|
onDrop: handleDrop,
|
|
})
|
|
|
|
return (
|
|
<div
|
|
{...getRootProps()}
|
|
className={cn(
|
|
'relative flex flex-col items-center justify-center gap-3 rounded-card border-2 border-dashed p-12 text-center transition-colors',
|
|
isDragActive
|
|
? 'border-volt bg-volt/5 text-volt'
|
|
: disabled
|
|
? 'border-charcoal/40 text-charcoal cursor-not-allowed opacity-50'
|
|
: 'border-charcoal/80 text-[#a0a0a0] hover:border-volt/60 hover:text-white cursor-pointer',
|
|
)}
|
|
>
|
|
<input {...getInputProps()} capture="environment" />
|
|
{isDragActive ? (
|
|
<Upload className="w-10 h-10 text-volt" />
|
|
) : (
|
|
<Camera className="w-10 h-10" />
|
|
)}
|
|
<div>
|
|
<p className="font-semibold text-sm">
|
|
{isDragActive
|
|
? 'Drop photos here'
|
|
: disabled
|
|
? 'Maximum 3 photos reached'
|
|
: 'Drag photos or tap to shoot'}
|
|
</p>
|
|
<p className="text-xs mt-1 opacity-70">
|
|
{disabled ? '' : `${remaining} of ${MAX} slots remaining · JPEG, PNG, WebP`}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|