import { PhotoGuide } from './PhotoGuide'; import { useId, useRef, useState } from 'react'; import { fileToBase64, resizeAvatarImage, UndecodableImageError } from '../imageUtils'; import { hasConsent, setConsent } from '../storage'; import type { WidgetTranslations } from '../i18n'; import { CameraCapture, cameraSupportIssue } from './CameraCapture'; interface AvatarUploadProps { onAvatarSaved: (imageData: string) => void; onCancel?: () => void; t: WidgetTranslations['upload']; // Pre-existing saved avatar (e.g. a returning visitor routed back here only // to re-confirm consent). Seeding the preview from it opens the component // directly in the preview+consent state instead of forcing a new file pick. initialPreview?: string | null; titleOverride?: string; descriptionOverride?: string; privacyNoteOverride?: string; consentOverride?: string; } export function AvatarUpload({ onAvatarSaved, onCancel, t, initialPreview, titleOverride, descriptionOverride, privacyNoteOverride, consentOverride }: AvatarUploadProps) { const fileInputRef = useRef(null); const fileInputId = useId(); const [preview, setPreview] = useState(initialPreview ?? null); const [isProcessing, setIsProcessing] = useState(false); const [error, setError] = useState(null); const [consented, setConsented] = useState(hasConsent()); const [cameraOpen, setCameraOpen] = useState(false); const [photoSource, setPhotoSource] = useState<'upload' | 'camera' | null>(null); const cameraIssue = cameraSupportIssue(); const isReusingSaved = preview !== null && preview === initialPreview; const processFile = async (file: File, source: 'upload' | 'camera') => { setError(null); try { setIsProcessing(true); const base64 = await fileToBase64(file); const resized = await resizeAvatarImage(base64); setPreview(resized); setPhotoSource(source); } catch (err) { // Refuse only when the image is genuinely undecodable; anything the // browser can decode is normalized and used. setError(err instanceof UndecodableImageError ? t.unreadableImage : t.processingFailed); console.error('Error processing image:', err); } finally { setIsProcessing(false); } }; const handleFileSelect = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) void processFile(file, 'upload'); }; const handleCameraCapture = (file: File) => { setCameraOpen(false); void processFile(file, 'camera'); }; const handleUseUpload = () => { setCameraOpen(false); requestAnimationFrame(() => fileInputRef.current?.focus()); }; const handleConfirm = () => { // Consent is required before we ever generate an AI try-on from the shopper's photo. if (preview && consented) { onAvatarSaved(preview); // Reset file input for next upload if (fileInputRef.current) { fileInputRef.current.value = ''; } } }; const handleConsentChange = (e: React.ChangeEvent) => { const checked = e.target.checked; setConsented(checked); if (checked) { setConsent(); } }; const handleRetake = () => { setPreview(null); setError(null); setCameraOpen(photoSource === 'camera'); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); const files = e.dataTransfer.files; if (files.length > 0) { const file = files[0]; // Simulate file input change event const event = { target: { files: { 0: file, length: 1 }, }, } as unknown as React.ChangeEvent; handleFileSelect(event); } }; return (

{isReusingSaved ? t.reuseTitle : titleOverride ?? t.title}

{!isReusingSaved &&

{descriptionOverride ?? t.description}

}
{error &&
{error}
} {!preview && cameraOpen ? ( ) : !preview ? ( <>
); }