Improve upload progress UX — indeterminate bar and elapsed time

Local uploads appeared stuck at 0% because onUploadProgress
doesn't fire granularly on fast local networks or when network
buffering makes the browser report instant completion.

Changes:
- Animated indeterminate progress bar when percent unavailable
- Elapsed seconds counter so user sees activity
- Separate "Uploading" vs "Processing on server" stages
- Hint message about large PDFs taking time

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Daniel 2026-04-19 19:17:33 +02:00
parent 56fdc57389
commit c44014c787

View file

@ -128,23 +128,30 @@ export default function UploadPage() {
const hasNextcloud = !!localStorage.getItem('nc_username')
const [uploadName, setUploadName] = useState('')
const [stage, setStage] = useState('uploading') // 'uploading' | 'processing'
const [elapsed, setElapsed] = useState(0)
const doUpload = async (f) => {
if (!f) return
setUploadName(f.name)
setUploading(true); setError(''); setProgress(0)
setUploading(true); setError(''); setProgress(0); setStage('uploading'); setElapsed(0)
const start = Date.now()
const timer = setInterval(() => setElapsed(Math.round((Date.now() - start) / 1000)), 500)
const formData = new FormData()
formData.append('file', f)
try {
const res = await api.post('/documents/upload', formData, {
onUploadProgress: (e) => {
if (e.progress != null) setProgress(Math.round(e.progress * 100))
else if (e.total) setProgress(Math.round((e.loaded / e.total) * 100))
const pct = e.progress != null
? Math.round(e.progress * 100)
: e.total ? Math.round((e.loaded / e.total) * 100) : 0
setProgress(pct)
if (pct >= 100) setStage('processing')
},
})
navigate(`/documents/${res.data.id}`)
} catch (err) {
setError(err.response?.data?.detail || 'Upload failed')
} finally { setUploading(false) }
} finally { clearInterval(timer); setUploading(false) }
}
const handleFile = (f, fromNextcloud = false) => {
@ -222,12 +229,29 @@ export default function UploadPage() {
{uploading && (
<div style={{ marginTop: 16, padding: '16px', background: 'var(--option-sel-bg)', borderRadius: 8, border: '1px solid var(--option-sel-bd)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8, fontSize: '0.9rem', fontWeight: 600 }}>
<span>Uploading {uploadName || file?.name || 'file'}...</span>
<span>{progress}%</span>
<span>
{stage === 'processing' ? 'Processing on server' : 'Uploading'} {uploadName || file?.name || 'file'}...
</span>
<span>
{progress > 0 ? `${progress}%` : ''} {elapsed > 0 && `(${elapsed}s)`}
</span>
</div>
<div className="progress-bar">
<div className="fill" style={{ width: `${Math.max(progress, 5)}%`, transition: 'width 0.3s' }} />
<div className="progress-bar" style={{ overflow: 'hidden', position: 'relative' }}>
{progress > 0 ? (
<div className="fill" style={{ width: `${progress}%`, transition: 'width 0.3s' }} />
) : (
<div style={{
height: '100%',
width: '30%',
background: 'var(--primary)',
animation: 'indeterminate 1.4s infinite linear',
}} />
)}
</div>
<style>{`@keyframes indeterminate { 0% { transform: translateX(-100%); } 100% { transform: translateX(400%); } }`}</style>
<p style={{ fontSize: '0.78rem', color: 'var(--text-muted)', marginTop: 8, marginBottom: 0 }}>
Large PDFs may take a few minutes please keep this tab open.
</p>
</div>
)}