feat(phase2): implement ItemPhotoUpload component and hook
This commit is contained in:
92
frontend/hooks/usePhotoUpload.ts
Normal file
92
frontend/hooks/usePhotoUpload.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { inventoryApi } from '@/lib/api';
|
||||
|
||||
interface Photo {
|
||||
thumbnail_url: string;
|
||||
full_url: string;
|
||||
uploaded_at: string;
|
||||
}
|
||||
|
||||
interface UsePhotoUploadReturn {
|
||||
upload: (file: File, itemId: number) => Promise<Photo>;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const ACCEPTED_MIME_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
|
||||
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
export function usePhotoUpload(): UsePhotoUploadReturn {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const validateFile = useCallback((file: File): { valid: boolean; error?: string } => {
|
||||
// Validate file size
|
||||
if (file.size > MAX_FILE_SIZE) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'File too large, max 10MB',
|
||||
};
|
||||
}
|
||||
|
||||
// Validate MIME type
|
||||
if (!ACCEPTED_MIME_TYPES.includes(file.type)) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Invalid image format',
|
||||
};
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}, []);
|
||||
|
||||
const upload = useCallback(
|
||||
async (file: File, itemId: number): Promise<Photo> => {
|
||||
setError(null);
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
// Validate file (synchronously)
|
||||
const validation = validateFile(file);
|
||||
if (!validation.valid) {
|
||||
const errorMsg = validation.error || 'File validation failed';
|
||||
setError(errorMsg);
|
||||
setIsLoading(false);
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
|
||||
// Create FormData for multipart upload
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
// Upload to backend
|
||||
const response = await inventoryApi.uploadItemPhoto(itemId, formData);
|
||||
|
||||
// Handle response
|
||||
if (response.status === 'ok' && response.photo) {
|
||||
const photo: Photo = {
|
||||
thumbnail_url: response.photo.thumbnail_url,
|
||||
full_url: response.photo.full_url,
|
||||
uploaded_at: response.photo.uploaded_at,
|
||||
};
|
||||
setIsLoading(false);
|
||||
return photo;
|
||||
}
|
||||
|
||||
throw new Error('Invalid response from server');
|
||||
} catch (err: any) {
|
||||
const errorMsg = err.message || 'Upload failed';
|
||||
setError(errorMsg);
|
||||
setIsLoading(false);
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
},
|
||||
[validateFile]
|
||||
);
|
||||
|
||||
return {
|
||||
upload,
|
||||
isLoading,
|
||||
error,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user