feat: Implement end-to-end photo upload functionality
Some checks failed
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled

- Add file input with hidden native picker
- Convert selected images to base64 data URLs
- Validate file type (images only) and size (max 5MB)
- Display error alerts for invalid uploads
- Show preview immediately after selection
- Update help text to indicate camera button functionality
- Handle image load/error states properly

Now clicking the camera button opens file picker and uploads work fully.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-04 08:19:51 +00:00
parent ac59e6fe82
commit 07d5d3e55c
2 changed files with 62 additions and 7 deletions

View File

@@ -1,6 +1,6 @@
'use client';
import { useState } from 'react';
import { useState, useRef } from 'react';
import {
Box,
Avatar,
@@ -8,6 +8,7 @@ import {
TextField,
Typography,
Paper,
Alert,
} from '@mui/material';
import { PhotoCamera, Person } from '@mui/icons-material';
@@ -27,6 +28,8 @@ export function PhotoUpload({
size = 100
}: PhotoUploadProps) {
const [imageError, setImageError] = useState(false);
const [uploadError, setUploadError] = useState<string>('');
const fileInputRef = useRef<HTMLInputElement>(null);
const handleImageError = () => {
setImageError(true);
@@ -36,6 +39,46 @@ export function PhotoUpload({
setImageError(false);
};
const handleFileSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
// Validate file type
if (!file.type.startsWith('image/')) {
setUploadError('Please select an image file');
return;
}
// Validate file size (max 5MB)
const maxSize = 5 * 1024 * 1024; // 5MB
if (file.size > maxSize) {
setUploadError('Image size must be less than 5MB');
return;
}
// Convert to base64
const reader = new FileReader();
reader.onload = (e) => {
const base64String = e.target?.result as string;
onChange(base64String);
setUploadError('');
setImageError(false);
};
reader.onerror = () => {
setUploadError('Failed to read image file');
};
reader.readAsDataURL(file);
// Reset input
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
const handleCameraClick = () => {
fileInputRef.current?.click();
};
return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, alignItems: 'center' }}>
<Paper
@@ -56,6 +99,12 @@ export function PhotoUpload({
{label}
</Typography>
{uploadError && (
<Alert severity="error" onClose={() => setUploadError('')} sx={{ width: '100%' }}>
{uploadError}
</Alert>
)}
<Box sx={{ position: 'relative' }}>
<Avatar
src={!imageError && value ? value : undefined}
@@ -71,6 +120,15 @@ export function PhotoUpload({
{!value || imageError ? <Person sx={{ fontSize: size / 2 }} /> : null}
</Avatar>
<input
ref={fileInputRef}
type="file"
accept="image/*"
style={{ display: 'none' }}
onChange={handleFileSelect}
disabled={disabled}
/>
<IconButton
sx={{
position: 'absolute',
@@ -85,10 +143,7 @@ export function PhotoUpload({
}}
size="small"
disabled={disabled}
onClick={() => {
// Future: Open file picker for actual upload
// For now, user can paste URL below
}}
onClick={handleCameraClick}
>
<PhotoCamera fontSize="small" />
</IconButton>
@@ -102,7 +157,7 @@ export function PhotoUpload({
size="small"
placeholder="https://example.com/photo.jpg"
disabled={disabled}
helperText="Paste an image URL or upload a photo"
helperText="Click camera to upload or paste an image URL"
/>
</Paper>
</Box>

File diff suppressed because one or more lines are too long