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:
@@ -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"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user