diff --git a/frontend/src/components/progress-bar.tsx b/frontend/src/components/progress-bar.tsx
new file mode 100644
index 0000000..55fc26c
--- /dev/null
+++ b/frontend/src/components/progress-bar.tsx
@@ -0,0 +1,34 @@
+import type { CSSProperties } from 'react';
+
+export default function ProgressBar({ progress }: { progress: number }) {
+ const containerStyles: CSSProperties = {
+ height: 20,
+ width: '100%',
+ backgroundColor: '#e0e0de',
+ borderRadius: 50,
+ marginTop: 10,
+ };
+
+ const fillerStyles: CSSProperties = {
+ height: '100%',
+ width: `${progress}%`,
+ backgroundColor: 'var(--primary)',
+ borderRadius: 'inherit',
+ textAlign: 'right',
+ transition: 'width 0.2s ease-in-out',
+ };
+
+ const labelStyles: CSSProperties = {
+ padding: 5,
+ color: 'white',
+ fontWeight: 'bold',
+ };
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/components/upload-component.tsx b/frontend/src/components/upload-component.tsx
index a10c665..6f19260 100644
--- a/frontend/src/components/upload-component.tsx
+++ b/frontend/src/components/upload-component.tsx
@@ -1,12 +1,13 @@
'use client'
-import {useState} from 'react'
-import {Cloud} from 'react-feather'
-import {useDropzone} from 'react-dropzone'
-import {toast} from 'sonner'
+import { useState } from 'react'
+import { useDropzone } from 'react-dropzone'
+import { Cloud } from 'react-feather'
+import { toast } from 'sonner'
-import {Button} from '@/components/ui/button'
-import {uploadDocuments} from '@/lib/documents'
+import { Button } from '@/components/ui/button'
+import { uploadDocuments } from '@/lib/documents'
+import ProgressBar from './progress-bar'
const ACCEPTED_FILE_TYPES = {
'application/pdf': ['.pdf'],
@@ -22,12 +23,35 @@ export default function UploadComponent({
callback?: () => void
}) {
const [isUploading, setIsUploading] = useState(false)
+ const [uploadProgress, setUploadProgress] = useState(0);
+
const {getRootProps, getInputProps, isDragActive} = useDropzone({
accept: ACCEPTED_FILE_TYPES,
maxSize: MAX_FILE_SIZE,
onDrop: async (documents) => {
setIsUploading(true)
- await uploadDocuments(courseId, documents)
+ await uploadDocuments(courseId, documents, (progressEvent) => {
+ if (progressEvent.total) {
+ const percentCompleted = Math.round(
+ (progressEvent.loaded * 100) / progressEvent.total,
+ );
+ setUploadProgress(percentCompleted);
+ }
+ })
+ .then((result) => {
+ if (result.ok) {
+ toast.success('Files uploaded successfully')
+ } else {
+ toast.error(result.error?.message || 'Failed to upload files')
+ }
+ })
+ .catch(() => {
+ toast.error('Failed to upload files')
+ })
+ .finally(async () => {
+ setUploadProgress(0); // Reset progress after upload completes
+ setIsUploading(false)
+ })
if (callback) {
await callback()
}
@@ -69,7 +93,12 @@ export default function UploadComponent({
>
-
+ {isUploading ? (
+
+ ) : (
{isDragActive ? 'Drop files here' : 'Drag and drop files here'}
@@ -87,7 +116,7 @@ export default function UploadComponent({
>
{isUploading ? 'Uploading...' : 'Browse Files'}
-
+
)}
)
}
diff --git a/frontend/src/lib/documents.ts b/frontend/src/lib/documents.ts
index e78da6f..516d079 100644
--- a/frontend/src/lib/documents.ts
+++ b/frontend/src/lib/documents.ts
@@ -1,11 +1,13 @@
-import {DocumentsService} from '@/client'
+import { DocumentsService } from '@/client'
-import {Result} from './result'
-import {mapApiError} from './mapApiError'
+import { AxiosProgressEvent } from 'axios'
+import { mapApiError } from './mapApiError'
+import { Result } from './result'
export async function uploadDocuments(
courseId: string,
documents: File[],
+ onUploadProgress?: (progressEvent: AxiosProgressEvent) => void,
): Promise> {
try {
const formData = new FormData()
@@ -22,6 +24,7 @@ export async function uploadDocuments(
// openapi-ts misinterpret this as a plain string rather than a File
// object. This bypasses the validation
requestValidator: async () => {},
+ onUploadProgress: onUploadProgress,
})
return {