feat(17-03): update client components to use imageUrl from API responses

- Replace all /uploads/ path construction with imageUrl presigned URLs
- Add imageUrl prop to ItemCard, CandidateCard, CandidateListItem, ComparisonTable
- Update ImageUpload to use presigned URLs + local preview for new uploads
- Pass imageUrl through from parent components (CollectionView, forms, routes)
This commit is contained in:
2026-04-05 12:27:34 +02:00
parent 2d31680072
commit 8c64bf9fbf
10 changed files with 42 additions and 11 deletions

View File

@@ -3,15 +3,17 @@ import { apiUpload } from "../lib/api";
interface ImageUploadProps {
value: string | null;
imageUrl?: string | null;
onChange: (filename: string | null) => void;
}
const MAX_SIZE_BYTES = 5 * 1024 * 1024; // 5MB
const ACCEPTED_TYPES = ["image/jpeg", "image/png", "image/webp"];
export function ImageUpload({ value, onChange }: ImageUploadProps) {
export function ImageUpload({ value, imageUrl, onChange }: ImageUploadProps) {
const [uploading, setUploading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [localPreview, setLocalPreview] = useState<string | null>(null);
const inputRef = useRef<HTMLInputElement>(null);
async function handleFileChange(e: React.ChangeEvent<HTMLInputElement>) {
@@ -30,12 +32,17 @@ export function ImageUpload({ value, onChange }: ImageUploadProps) {
return;
}
// Create local preview for immediate display
const previewUrl = URL.createObjectURL(file);
setLocalPreview(previewUrl);
setUploading(true);
try {
const result = await apiUpload<{ filename: string }>("/api/images", file);
onChange(result.filename);
} catch {
setError("Upload failed. Please try again.");
setLocalPreview(null);
} finally {
setUploading(false);
// Reset input so the same file can be re-selected
@@ -45,9 +52,14 @@ export function ImageUpload({ value, onChange }: ImageUploadProps) {
function handleRemove(e: React.MouseEvent) {
e.stopPropagation();
setLocalPreview(null);
onChange(null);
}
// Determine the display URL: local preview takes priority (just-uploaded),
// then presigned URL from API, then nothing
const displayUrl = localPreview || imageUrl || null;
return (
<div>
{/* Hero image area */}
@@ -55,10 +67,10 @@ export function ImageUpload({ value, onChange }: ImageUploadProps) {
onClick={() => inputRef.current?.click()}
className="relative w-full aspect-[4/3] rounded-xl overflow-hidden cursor-pointer group"
>
{value ? (
{displayUrl ? (
<>
<img
src={`/uploads/${value}`}
src={displayUrl}
alt="Item"
className="w-full h-full object-cover"
/>