legal documents improved, gäste-pwa uploads optimiert: client-side compression/resize.

This commit is contained in:
Codex Agent
2025-11-20 22:09:11 +01:00
parent b8cd32c030
commit e0127e7f39
8 changed files with 197 additions and 76 deletions

View File

@@ -32,6 +32,7 @@ import { useTranslation, type TranslateFn } from '../i18n/useTranslation';
import { buildLimitSummaries, type LimitSummaryCard } from '../lib/limitSummaries';
import { resolveUploadErrorDialog, type UploadErrorDialog } from '../lib/uploadErrorDialog';
import { useEventStats } from '../context/EventStatsContext';
import { compressPhoto, formatBytes } from '../lib/image';
interface Task {
id: number;
@@ -201,6 +202,8 @@ const [canUpload, setCanUpload] = useState(true);
let active = true;
async function loadTask() {
if (taskId === null) return;
const currentTaskId = taskId;
const fallbackTitle = t('upload.taskInfo.fallbackTitle').replace('{id}', `${currentTaskId}`);
const fallbackDescription = t('upload.taskInfo.fallbackDescription');
@@ -553,19 +556,57 @@ const [canUpload, setCanUpload] = useState(true);
const handleUsePhoto = useCallback(async () => {
if (!eventKey || !reviewPhoto || !task || !canUpload) return;
setMode('uploading');
setUploadProgress(5);
setUploadProgress(2);
setUploadError(null);
setUploadWarning(null);
setStatusMessage(t('upload.status.preparing'));
if (uploadProgressTimerRef.current) {
window.clearInterval(uploadProgressTimerRef.current);
uploadProgressTimerRef.current = null;
}
uploadProgressTimerRef.current = window.setInterval(() => {
setUploadProgress((prev) => (prev < 90 ? prev + 5 : prev));
}, 400);
const maxEdge = 2400;
const targetBytes = 4_000_000;
let fileForUpload = reviewPhoto.file;
try {
const photoId = await uploadPhoto(eventKey, reviewPhoto.file, task.id, emotionSlug || undefined);
setStatusMessage(t('upload.status.optimizing', 'Foto wird optimiert…'));
const optimized = await compressPhoto(reviewPhoto.file, {
maxEdge,
targetBytes,
qualityStart: 0.82,
});
fileForUpload = optimized;
setUploadProgress(10);
if (optimized.size < reviewPhoto.file.size - 50_000) {
const saved = formatBytes(reviewPhoto.file.size - optimized.size);
setUploadWarning(
t('upload.optimizedNotice', 'Wir haben dein Foto verkleinert, damit der Upload schneller klappt. Eingespart: {saved}')
.replace('{saved}', saved)
);
}
} catch (e) {
console.warn('Image optimization failed, uploading original', e);
setUploadWarning(t('upload.optimizedFallback', 'Optimierung nicht möglich wir laden das Original hoch.'));
}
try {
const photoId = await uploadPhoto(eventKey, fileForUpload, task.id, emotionSlug || undefined, {
maxRetries: 2,
onProgress: (percent) => {
setUploadProgress(Math.max(15, Math.min(98, percent)));
setStatusMessage(t('upload.status.uploading'));
},
onRetry: (attempt) => {
setUploadWarning(
t('upload.retrying', 'Verbindung holperig neuer Versuch ({attempt}).')
.replace('{attempt}', `${attempt}`)
);
},
});
setUploadProgress(100);
setStatusMessage(t('upload.status.completed'));
markCompleted(task.id);
@@ -580,6 +621,12 @@ const [canUpload, setCanUpload] = useState(true);
setErrorDialog(dialog);
setUploadError(dialog.description);
if (uploadErr.status === 422 || uploadErr.code === 'validation_error') {
setUploadWarning(
t('upload.errors.tooLargeHint', 'Das Foto war zu groß. Bitte erneut versuchen wir verkleinern es automatisch.')
);
}
if (
uploadErr.code === 'photo_limit_exceeded'
|| uploadErr.code === 'upload_device_limit'
@@ -592,10 +639,6 @@ const [canUpload, setCanUpload] = useState(true);
setMode('review');
} finally {
if (uploadProgressTimerRef.current) {
window.clearInterval(uploadProgressTimerRef.current);
uploadProgressTimerRef.current = null;
}
setStatusMessage('');
}
}, [emotionSlug, markCompleted, navigateAfterUpload, reviewPhoto, eventKey, stopStream, task, canUpload, t]);