PawTrace / frontend /src /components /PhotoUpload.tsx
Elliott Duke
Site polish: hero illustration, About rewrite, copy and styling pass
9122959
Raw
History Blame Contribute Delete
2.21 kB
import { useRef, useState } from "react";
interface Props {
onChange: (files: File[]) => void;
max?: number;
existing?: number; // photos the dog already has — the counter/limit are out of `max` total
}
// Multi-photo picker with previews. On mobile, `capture="environment"` opens the rear camera
// directly (spec §13).
export default function PhotoUpload({ onChange, max = 8, existing = 0 }: Props) {
const [files, setFiles] = useState<File[]>([]);
const inputRef = useRef<HTMLInputElement>(null);
const room = Math.max(0, max - existing); // how many NEW photos still fit
function addFiles(list: FileList | null) {
if (!list) return;
const next = [...files, ...Array.from(list)].slice(0, room);
setFiles(next);
onChange(next);
}
function removeAt(i: number) {
const next = files.filter((_, idx) => idx !== i);
setFiles(next);
onChange(next);
}
return (
<div>
<div className="flex flex-wrap gap-3 mb-3">
{files.map((f, i) => (
<div key={i} className="relative">
<img
src={URL.createObjectURL(f)}
alt={`preview ${i + 1}`}
className="h-24 w-24 rounded-md object-cover border border-gray-200"
/>
<button
type="button"
onClick={() => removeAt(i)}
className="absolute -top-2 -right-2 h-6 w-6 rounded-full bg-red-600 text-white text-xs"
aria-label="Remove photo"
>
×
</button>
</div>
))}
</div>
<input
ref={inputRef}
type="file"
accept="image/*"
capture="environment"
multiple
className="hidden"
onChange={(e) => addFiles(e.target.files)}
/>
<button
type="button"
className="btn-secondary"
onClick={() => inputRef.current?.click()}
disabled={existing + files.length >= max}
>
Add photos ({existing + files.length}/{max})
</button>
<p className="text-xs text-gray-500 mt-1">
On a phone this opens your camera. Clear, well-lit photos of the whole dog match best.
</p>
</div>
);
}