progress bar + better ui
This commit is contained in:
112
nextjs/app/manage/_components/upload-progress-form.js
Normal file
112
nextjs/app/manage/_components/upload-progress-form.js
Normal file
@@ -0,0 +1,112 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
function parseErrorMessage(xhr) {
|
||||
const response = xhr.response;
|
||||
if (response && typeof response === 'object' && response.error) {
|
||||
return String(response.error);
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(xhr.responseText || '{}');
|
||||
if (parsed && typeof parsed.error === 'string') {
|
||||
return parsed.error;
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
|
||||
return `Upload fehlgeschlagen (HTTP ${xhr.status}).`;
|
||||
}
|
||||
|
||||
export function UploadProgressForm({ csrfToken }) {
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [localError, setLocalError] = useState('');
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
if (uploading) {
|
||||
return;
|
||||
}
|
||||
|
||||
const form = event.currentTarget;
|
||||
const formData = new FormData(form);
|
||||
const uploadedFile = formData.get('file');
|
||||
|
||||
if (!uploadedFile || typeof uploadedFile === 'string' || !uploadedFile.size) {
|
||||
setLocalError('Bitte zuerst eine Datei auswählen.');
|
||||
return;
|
||||
}
|
||||
|
||||
setUploading(true);
|
||||
setProgress(0);
|
||||
setLocalError('');
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/manage/api/upload', true);
|
||||
xhr.responseType = 'json';
|
||||
xhr.setRequestHeader('x-csrf-token', csrfToken);
|
||||
|
||||
xhr.upload.onprogress = (uploadEvent) => {
|
||||
if (!uploadEvent.lengthComputable || uploadEvent.total <= 0) {
|
||||
return;
|
||||
}
|
||||
const nextValue = Math.round((uploadEvent.loaded / uploadEvent.total) * 100);
|
||||
setProgress(Math.max(0, Math.min(100, nextValue)));
|
||||
};
|
||||
|
||||
xhr.onerror = () => {
|
||||
setUploading(false);
|
||||
setLocalError('Netzwerkfehler beim Upload.');
|
||||
};
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
const redirectPath = xhr.response?.redirect || '/manage/dashboard?success=Upload%20abgeschlossen.';
|
||||
window.location.assign(redirectPath);
|
||||
return;
|
||||
}
|
||||
|
||||
setUploading(false);
|
||||
setProgress(0);
|
||||
setLocalError(parseErrorMessage(xhr));
|
||||
};
|
||||
|
||||
xhr.send(formData);
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="form-grid" onSubmit={handleSubmit} encType="multipart/form-data">
|
||||
<input type="hidden" name="csrfToken" value={csrfToken} />
|
||||
|
||||
<label className="field">
|
||||
Datei
|
||||
<input className="input" name="file" type="file" required disabled={uploading} />
|
||||
</label>
|
||||
|
||||
<label className="field">
|
||||
Aufbewahrung in Stunden
|
||||
<input className="input" name="retentionHours" placeholder="168" disabled={uploading} />
|
||||
</label>
|
||||
|
||||
{uploading ? (
|
||||
<div className="upload-progress" role="status" aria-live="polite">
|
||||
<div className="upload-progress-row">
|
||||
<span className="muted">Upload läuft …</span>
|
||||
<strong>{progress}%</strong>
|
||||
</div>
|
||||
<div className="upload-progress-track" aria-hidden="true">
|
||||
<div className="upload-progress-fill" style={{ width: `${progress}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{localError ? <div className="status error">{localError}</div> : null}
|
||||
|
||||
<button className="btn" type="submit" disabled={uploading}>
|
||||
{uploading ? 'Wird hochgeladen …' : 'Hochladen'}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user