import { useCallback, useState } from 'react'; import { compressPhoto, formatBytes } from '../lib/image'; import { uploadPhoto, type UploadError } from '../services/photosApi'; import { useGuestIdentity } from '../context/GuestIdentityContext'; import { useGuestTaskProgress } from '../hooks/useGuestTaskProgress'; import { resolveUploadErrorDialog, type UploadErrorDialog } from '../lib/uploadErrorDialog'; type DirectUploadResult = { success: boolean; photoId?: number; warning?: string | null; error?: string | null; dialog?: UploadErrorDialog | null; }; type UseDirectUploadOptions = { eventToken: string; taskId?: number | null; emotionSlug?: string; onCompleted?: (photoId: number) => void; }; export function useDirectUpload({ eventToken, taskId, emotionSlug, onCompleted }: UseDirectUploadOptions) { const { name } = useGuestIdentity(); const { markCompleted } = useGuestTaskProgress(eventToken); const [uploading, setUploading] = useState(false); const [progress, setProgress] = useState(0); const [warning, setWarning] = useState(null); const [error, setError] = useState(null); const [errorDialog, setErrorDialog] = useState(null); const [canUpload, setCanUpload] = useState(true); const reset = useCallback(() => { setProgress(0); setWarning(null); setError(null); setErrorDialog(null); }, []); const preparePhoto = useCallback(async (file: File) => { reset(); let prepared = file; try { prepared = await compressPhoto(file, { maxEdge: 2400, targetBytes: 4_000_000, qualityStart: 0.82, }); if (prepared.size < file.size - 50_000) { const saved = formatBytes(file.size - prepared.size); setWarning(`Wir haben dein Foto verkleinert, damit der Upload schneller klappt. Eingespart: ${saved}`); } } catch (err) { console.warn('Direct upload: optimization failed, using original', err); setWarning('Optimierung nicht möglich – wir laden das Original hoch.'); } if (prepared.size > 12_000_000) { setError('Das Foto war zu groß. Bitte erneut versuchen – wir verkleinern es automatisch.'); return { ok: false as const }; } return { ok: true as const, prepared }; }, [reset]); const upload = useCallback( async (file: File): Promise => { if (!canUpload || uploading) return { success: false, warning, error }; const preparedResult = await preparePhoto(file); if (!preparedResult.ok) { return { success: false, warning, error }; } const prepared = preparedResult.prepared; setUploading(true); setProgress(2); setError(null); setErrorDialog(null); try { const photoId = await uploadPhoto(eventToken, prepared, taskId ?? undefined, emotionSlug || undefined, { maxRetries: 2, guestName: name || undefined, onProgress: (percent) => { setProgress(Math.max(10, Math.min(98, percent))); }, onRetry: (attempt) => { setWarning(`Verbindung holperig – neuer Versuch (${attempt}).`); }, }); setProgress(100); if (taskId) { markCompleted(taskId); } try { const raw = localStorage.getItem('my-photo-ids'); const arr: number[] = raw ? JSON.parse(raw) : []; if (photoId && !arr.includes(photoId)) { localStorage.setItem('my-photo-ids', JSON.stringify([photoId, ...arr])); } } catch (persistErr) { console.warn('Direct upload: persist my-photo-ids failed', persistErr); } onCompleted?.(photoId); return { success: true, photoId, warning }; } catch (err) { console.error('Direct upload failed', err); const uploadErr = err as UploadError; const meta = uploadErr.meta as Record | undefined; const dialog = resolveUploadErrorDialog(uploadErr.code, meta, (v: string) => v); setErrorDialog(dialog); setError(dialog?.description ?? uploadErr.message ?? 'Upload fehlgeschlagen.'); setWarning(null); if ( uploadErr.code === 'photo_limit_exceeded' || uploadErr.code === 'upload_device_limit' || uploadErr.code === 'event_package_missing' || uploadErr.code === 'event_not_found' || uploadErr.code === 'gallery_expired' ) { setCanUpload(false); } if (uploadErr.status === 422 || uploadErr.code === 'validation_error') { setWarning('Das Foto war zu groß. Bitte erneut versuchen – wir verkleinern es automatisch.'); } return { success: false, warning, error: dialog?.description ?? uploadErr.message, dialog }; } finally { setUploading(false); setProgress((p) => (p === 100 ? p : 0)); } }, [canUpload, emotionSlug, eventToken, markCompleted, name, preparePhoto, taskId, uploading, warning, onCompleted] ); return { upload, uploading, progress, warning, error, errorDialog, reset, }; }