feat: add upload progress bars and error toasts
Adds uploadWithProgress() using XMLHttpRequest for real-time upload progress events. MessageInput now shows per-file progress bars with percentage overlay during upload. Failed uploads show toast warnings with the specific error instead of silently failing. Upload timeout raised to 10 minutes for large files.
This commit is contained in:
@@ -128,6 +128,7 @@ export class BackspaceApiClient {
|
||||
|
||||
readonly uploads: {
|
||||
upload: (file: File) => Promise<Attachment>;
|
||||
uploadWithProgress: (file: File, onProgress: (loaded: number, total: number) => void) => Promise<Attachment>;
|
||||
url: (filename: string) => string;
|
||||
};
|
||||
|
||||
@@ -313,6 +314,44 @@ export class BackspaceApiClient {
|
||||
return response.json() as Promise<Attachment>;
|
||||
}
|
||||
|
||||
function uploadFileWithProgress(file: File, onProgress: (loaded: number, total: number) => void): Promise<Attachment> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
xhr.upload.addEventListener('progress', (e) => {
|
||||
if (e.lengthComputable) onProgress(e.loaded, e.total);
|
||||
});
|
||||
|
||||
xhr.addEventListener('load', () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
try { resolve(JSON.parse(xhr.responseText)); }
|
||||
catch { reject(new Error('Invalid server response')); }
|
||||
} else if (xhr.status === 401 && onUnauthorized) {
|
||||
onUnauthorized();
|
||||
reject(new Error('Unauthorized'));
|
||||
} else if (xhr.status === 413) {
|
||||
reject(new Error('File too large'));
|
||||
} else {
|
||||
try {
|
||||
const body = JSON.parse(xhr.responseText);
|
||||
reject(new Error(body.error || `Upload failed (${xhr.status})`));
|
||||
} catch { reject(new Error(`Upload failed (${xhr.status})`)); }
|
||||
}
|
||||
});
|
||||
|
||||
xhr.addEventListener('error', () => reject(new Error('Upload failed — network error')));
|
||||
xhr.addEventListener('timeout', () => reject(new Error('Upload timed out')));
|
||||
|
||||
xhr.open('POST', `${baseUrl}/uploads`);
|
||||
xhr.timeout = 10 * 60 * 1000; // 10 minutes for large files
|
||||
const token = getToken();
|
||||
if (token) xhr.setRequestHeader('Authorization', `Bearer ${token}`);
|
||||
xhr.send(formData);
|
||||
});
|
||||
}
|
||||
|
||||
this.auth = {
|
||||
register: (data: RegisterRequest) =>
|
||||
request<AuthResponse>('POST', '/auth/register', data, false),
|
||||
@@ -428,6 +467,7 @@ export class BackspaceApiClient {
|
||||
|
||||
this.uploads = {
|
||||
upload: uploadFile,
|
||||
uploadWithProgress: uploadFileWithProgress,
|
||||
url: (filename: string) => `${baseUrl}/uploads/${filename}`,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user