aayushprsingh commited on
Commit
418269a
·
1 Parent(s): d6e540c

feat(ui): add toast notifications for document uploads (#93)

Browse files
frontend/package-lock.json CHANGED
@@ -27,6 +27,7 @@
27
  "rehype-highlight": "^7.0.2",
28
  "remark-gfm": "^4.0.1",
29
  "shadcn": "^4.3.1",
 
30
  "tailwind-merge": "^3.5.0",
31
  "tw-animate-css": "^1.4.0",
32
  "zustand": "^5.0.13"
@@ -10486,6 +10487,16 @@
10486
  "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
10487
  "license": "MIT"
10488
  },
 
 
 
 
 
 
 
 
 
 
10489
  "node_modules/source-map": {
10490
  "version": "0.6.1",
10491
  "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
 
27
  "rehype-highlight": "^7.0.2",
28
  "remark-gfm": "^4.0.1",
29
  "shadcn": "^4.3.1",
30
+ "sonner": "^2.0.7",
31
  "tailwind-merge": "^3.5.0",
32
  "tw-animate-css": "^1.4.0",
33
  "zustand": "^5.0.13"
 
10487
  "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
10488
  "license": "MIT"
10489
  },
10490
+ "node_modules/sonner": {
10491
+ "version": "2.0.7",
10492
+ "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
10493
+ "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
10494
+ "license": "MIT",
10495
+ "peerDependencies": {
10496
+ "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
10497
+ "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
10498
+ }
10499
+ },
10500
  "node_modules/source-map": {
10501
  "version": "0.6.1",
10502
  "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
frontend/package.json CHANGED
@@ -30,6 +30,7 @@
30
  "rehype-highlight": "^7.0.2",
31
  "remark-gfm": "^4.0.1",
32
  "shadcn": "^4.3.1",
 
33
  "tailwind-merge": "^3.5.0",
34
  "tw-animate-css": "^1.4.0",
35
  "zustand": "^5.0.13"
 
30
  "rehype-highlight": "^7.0.2",
31
  "remark-gfm": "^4.0.1",
32
  "shadcn": "^4.3.1",
33
+ "sonner": "^2.0.7",
34
  "tailwind-merge": "^3.5.0",
35
  "tw-animate-css": "^1.4.0",
36
  "zustand": "^5.0.13"
frontend/src/app/dashboard/page.tsx CHANGED
@@ -4,6 +4,7 @@ import { useEffect, useState, useCallback } from "react";
4
  import dynamic from "next/dynamic";
5
  import { useRouter } from "next/navigation";
6
  import { useAuth } from "@/lib/auth";
 
7
  import { api, CONNECTION_ERROR_BANNER_MESSAGE, CONNECTION_ERROR_MESSAGE } from "@/lib/api";
8
  import Header from "@/components/layout/Header";
9
  import DocumentSidebar from "@/components/document/DocumentSidebar";
@@ -57,6 +58,7 @@ export default function DashboardPage() {
57
  const router = useRouter();
58
 
59
  const [documents, setDocuments] = useState<DocInfo[]>([]);
 
60
  const [activeDoc, setActiveDoc] = useState<DocInfo | null>(null);
61
  const [pdfPage, setPdfPage] = useState(1);
62
  const [sidebarOpen, setSidebarOpen] = useState(true);
@@ -105,6 +107,24 @@ export default function DashboardPage() {
105
  })();
106
  }, [user, loadDocuments]);
107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  // Poll for processing status
109
  useEffect(() => {
110
  const hasPending = (documents || []).some(
 
4
  import dynamic from "next/dynamic";
5
  import { useRouter } from "next/navigation";
6
  import { useAuth } from "@/lib/auth";
7
+ import { toast } from "sonner";
8
  import { api, CONNECTION_ERROR_BANNER_MESSAGE, CONNECTION_ERROR_MESSAGE } from "@/lib/api";
9
  import Header from "@/components/layout/Header";
10
  import DocumentSidebar from "@/components/document/DocumentSidebar";
 
58
  const router = useRouter();
59
 
60
  const [documents, setDocuments] = useState<DocInfo[]>([]);
61
+ const [prevDocs, setPrevDocs] = useState<Record<string, string>>({});
62
  const [activeDoc, setActiveDoc] = useState<DocInfo | null>(null);
63
  const [pdfPage, setPdfPage] = useState(1);
64
  const [sidebarOpen, setSidebarOpen] = useState(true);
 
107
  })();
108
  }, [user, loadDocuments]);
109
 
110
+ // Ingest status change toast notification handler
111
+ useEffect(() => {
112
+ const nextPrevDocs: Record<string, string> = {};
113
+ (documents || []).forEach((doc) => {
114
+ nextPrevDocs[doc.id] = doc.status;
115
+
116
+ const oldStatus = prevDocs[doc.id];
117
+ if (oldStatus && oldStatus !== doc.status) {
118
+ if (doc.status === "ready") {
119
+ toast.success(`🎉 Ingestion complete: '${doc.original_name}' is ready!`);
120
+ } else if (doc.status === "failed") {
121
+ toast.error(`❌ Ingestion failed for '${doc.original_name}': ${doc.error_message || "Unknown error"}`);
122
+ }
123
+ }
124
+ });
125
+ setPrevDocs(nextPrevDocs);
126
+ }, [documents, prevDocs]);
127
+
128
  // Poll for processing status
129
  useEffect(() => {
130
  const hasPending = (documents || []).some(
frontend/src/app/layout.tsx CHANGED
@@ -5,6 +5,7 @@ import { AuthProvider } from "@/lib/auth";
5
  import { TooltipProvider } from "@/components/ui/tooltip";
6
  import I18nProvider from "@/components/providers/I18nProvider";
7
  import { ThemeProvider } from "@/components/layout/ThemeProvider";
 
8
 
9
  const inter = Inter({
10
  variable: "--font-sans",
@@ -35,7 +36,10 @@ export default function RootLayout({
35
  >
36
  <AuthProvider>
37
  <I18nProvider>
38
- <TooltipProvider>{children}</TooltipProvider>
 
 
 
39
  </I18nProvider>
40
  </AuthProvider>
41
  </ThemeProvider>
 
5
  import { TooltipProvider } from "@/components/ui/tooltip";
6
  import I18nProvider from "@/components/providers/I18nProvider";
7
  import { ThemeProvider } from "@/components/layout/ThemeProvider";
8
+ import { Toaster } from "sonner";
9
 
10
  const inter = Inter({
11
  variable: "--font-sans",
 
36
  >
37
  <AuthProvider>
38
  <I18nProvider>
39
+ <TooltipProvider>
40
+ {children}
41
+ <Toaster richColors position="top-right" closeButton />
42
+ </TooltipProvider>
43
  </I18nProvider>
44
  </AuthProvider>
45
  </ThemeProvider>
frontend/src/components/document/DocumentSidebar.tsx CHANGED
@@ -12,6 +12,7 @@ import {
12
  FileText, Upload, Trash2, FileCheck, Clock, AlertCircle, Loader2, FolderOpen,
13
  } from "lucide-react";
14
  import { useDropzone } from "react-dropzone";
 
15
 
16
  interface Props {
17
  documents: DocInfo[];
@@ -38,15 +39,20 @@ export default function DocumentSidebar({ documents = [], activeDoc, onSelectDoc
38
 
39
  try {
40
  for (let i = 0; i < acceptedFiles.length; i++) {
 
41
  const formData = new FormData();
42
- formData.append("file", acceptedFiles[i]);
 
 
43
  await api.postForm("/api/v1/documents/upload", formData);
44
  setUploadProgress(((i + 1) / acceptedFiles.length) * 100);
 
45
  }
46
  onDocumentsChange();
47
  } catch (err) {
48
  const message = err instanceof Error ? err.message : t("documents.uploadFailed");
49
  setUploadError(message);
 
50
  } finally {
51
  setUploading(false);
52
  setUploadProgress(0);
 
12
  FileText, Upload, Trash2, FileCheck, Clock, AlertCircle, Loader2, FolderOpen,
13
  } from "lucide-react";
14
  import { useDropzone } from "react-dropzone";
15
+ import { toast } from "sonner";
16
 
17
  interface Props {
18
  documents: DocInfo[];
 
39
 
40
  try {
41
  for (let i = 0; i < acceptedFiles.length; i++) {
42
+ const file = acceptedFiles[i];
43
  const formData = new FormData();
44
+ formData.append("file", file);
45
+
46
+ toast.info(`⏳ Uploading '${file.name}'...`);
47
  await api.postForm("/api/v1/documents/upload", formData);
48
  setUploadProgress(((i + 1) / acceptedFiles.length) * 100);
49
+ toast.success(`📤 '${file.name}' uploaded successfully! Ingestion started.`);
50
  }
51
  onDocumentsChange();
52
  } catch (err) {
53
  const message = err instanceof Error ? err.message : t("documents.uploadFailed");
54
  setUploadError(message);
55
+ toast.error(`❌ Upload failed: ${message}`);
56
  } finally {
57
  setUploading(false);
58
  setUploadProgress(0);