// ProfilePhotoUpload Component // Editable profile photo with upload, preview, and validation function ProfilePhotoUpload({ currentPhotoUrl, userName, onPhotoUpdate, token }) { const [uploading, setUploading] = useState(false); const [preview, setPreview] = useState(null); const [error, setError] = useState(null); const [success, setSuccess] = useState(false); const fileInputRef = React.useRef(null); const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB const ALLOWED_TYPES = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/webp']; const handleFileSelect = async (file) => { setError(null); setSuccess(false); // Validate file type if (!ALLOWED_TYPES.includes(file.type)) { setError('Please upload an image file (PNG, JPG, GIF, WebP)'); return; } // Validate file size if (file.size > MAX_FILE_SIZE) { setError('File size must be under 5MB'); return; } // Show preview const reader = new FileReader(); reader.onloadend = () => setPreview(reader.result); reader.readAsDataURL(file); // Upload immediately await uploadPhoto(file); }; const uploadPhoto = async (file) => { setUploading(true); setError(null); try { const formData = new FormData(); formData.append('photo', file); const response = await fetch(`${API_URL}/profile/photo`, { method: 'PATCH', headers: { 'Authorization': `Bearer ${token}` }, body: formData }); const data = await response.json(); if (!response.ok) { throw new Error(data.detail || 'Upload failed'); } setSuccess(true); setPreview(null); // Notify parent component if (onPhotoUpdate) { onPhotoUpdate(data.photo_url); } setTimeout(() => setSuccess(false), 3000); } catch (err) { setError(err.message); } finally { setUploading(false); } }; const handleRemovePhoto = async () => { if (!confirm('Remove your profile photo?')) return; setUploading(true); setError(null); try { const response = await fetch(`${API_URL}/profile/photo`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }); const data = await response.json(); if (!response.ok) { throw new Error(data.detail || 'Failed to remove photo'); } if (onPhotoUpdate) { onPhotoUpdate(null); } setSuccess(true); setTimeout(() => setSuccess(false), 3000); } catch (err) { setError(err.message); } finally { setUploading(false); } }; return (
{/* Photo Display */}
{ const file = e.target.files[0]; if (file) handleFileSelect(file); }} />
fileInputRef.current?.click()} style={{ cursor: 'pointer', position: 'relative', transition: 'opacity 0.2s' }} onMouseEnter={(e) => e.currentTarget.style.opacity = '0.8'} onMouseLeave={(e) => e.currentTarget.style.opacity = '1'} > {preview && (
Preview
)} {/* Upload overlay */}
📷
{/* Action Buttons */}
{currentPhotoUrl && ( )}
{/* Status Messages */} {uploading && (
Uploading...
)} {error && (
{error}
)} {success && (
✓ Photo updated successfully!
)} {/* Hint Text */} {/*

Click to upload or change your photo. Max 5MB. Formats: PNG, JPG, GIF, WebP

*/}
); } // Export to global scope window.ProfilePhotoUpload = ProfilePhotoUpload;