@@ -38,7 +54,9 @@ export default async function DatabaseDetailLayout({
Back to Databases
-
{databaseName}
+
+ {loading ? "Loading..." : databaseName}
+
{status}
diff --git a/frontend/src/app/dashboard/databases/[dbId]/overview/page.tsx b/frontend/src/app/dashboard/databases/[dbId]/overview/page.tsx
index 24f63f3..e10237c 100644
--- a/frontend/src/app/dashboard/databases/[dbId]/overview/page.tsx
+++ b/frontend/src/app/dashboard/databases/[dbId]/overview/page.tsx
@@ -1,23 +1,50 @@
-import { getDatabase, getDatabaseSchema } from "@/lib/api";
+"use client";
+
+import { useEffect, useState } from "react";
+import { useParams } from "next/navigation";
+import { useApi } from "@/hooks/use-api";
import { Database, Table2 } from "lucide-react";
+import type { DatabaseResponse, SchemaDataResponse } from "@/types/api";
-export default async function DatabaseOverviewPage({
- params,
-}: {
- params: Promise<{ dbId: string }>;
-}) {
- const { dbId } = await params;
+export default function DatabaseOverviewPage() {
+ const params = useParams();
+ const dbId = params.dbId as string;
const id = parseInt(dbId, 10);
- let database;
- let schema;
- let error;
+ const [database, setDatabase] = useState
(null);
+ const [schema, setSchema] = useState(null);
+ const [error, setError] = useState();
+ const [loading, setLoading] = useState(true);
+ const api = useApi();
+
+
+ useEffect(() => {
+ async function fetchData() {
+ try {
+ const [dbData, schemaData] = await Promise.all([
+ api.getDatabase(id),
+ api.getDatabaseSchema(id),
+ ]);
+ setDatabase(dbData);
+ setSchema(schemaData);
+ } catch (e) {
+ const errorMessage = e instanceof Error ? e.message : "Failed to load database";
+ setError(errorMessage);
+ console.error("Error fetching database:", e);
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ fetchData();
+ }, [id, api]);
- try {
- [database, schema] = await Promise.all([getDatabase(id), getDatabaseSchema(id)]);
- } catch (e) {
- error = e instanceof Error ? e.message : "Failed to load database";
- console.error("Error fetching database:", e);
+ if (loading) {
+ return (
+
+
Loading database overview...
+
+ );
}
if (error || !database) {
diff --git a/frontend/src/app/dashboard/databases/[dbId]/schema/page.tsx b/frontend/src/app/dashboard/databases/[dbId]/schema/page.tsx
index 1ded2ee..0902644 100644
--- a/frontend/src/app/dashboard/databases/[dbId]/schema/page.tsx
+++ b/frontend/src/app/dashboard/databases/[dbId]/schema/page.tsx
@@ -1,21 +1,44 @@
-import { getDatabaseSchema } from "@/lib/api";
+"use client";
-export default async function DatabaseSchemaPage({
- params,
-}: {
- params: Promise<{ dbId: string }>;
-}) {
- const { dbId } = await params;
+import { useEffect, useState } from "react";
+import { useParams } from "next/navigation";
+import { useApi } from "@/hooks/use-api";
+import type { SchemaDataResponse } from "@/types/api";
+
+export default function DatabaseSchemaPage() {
+ const params = useParams();
+ const dbId = params.dbId as string;
const id = parseInt(dbId, 10);
- let schema;
- let error;
+ const [schema, setSchema] = useState(null);
+ const [error, setError] = useState();
+ const [loading, setLoading] = useState(true);
+ const api = useApi();
+
+
+ useEffect(() => {
+ async function fetchSchema() {
+ try {
+ const data = await api.getDatabaseSchema(id);
+ setSchema(data);
+ } catch (e) {
+ const errorMessage = e instanceof Error ? e.message : "Failed to load schema";
+ setError(errorMessage);
+ console.error("Error fetching schema:", e);
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ fetchSchema();
+ }, [id, api]);
- try {
- schema = await getDatabaseSchema(id);
- } catch (e) {
- error = e instanceof Error ? e.message : "Failed to load schema";
- console.error("Error fetching schema:", e);
+ if (loading) {
+ return (
+
+ );
}
if (error || !schema) {
diff --git a/frontend/src/app/dashboard/databases/[dbId]/settings/page.tsx b/frontend/src/app/dashboard/databases/[dbId]/settings/page.tsx
index e7ad7de..af8bb53 100644
--- a/frontend/src/app/dashboard/databases/[dbId]/settings/page.tsx
+++ b/frontend/src/app/dashboard/databases/[dbId]/settings/page.tsx
@@ -1,20 +1,43 @@
-import { getDatabase } from "@/lib/api";
+"use client";
-export default async function SettingsPage({
- params,
-}: {
- params: Promise<{ dbId: string }>;
-}) {
- const { dbId } = await params;
+import { useEffect, useState } from "react";
+import { useParams } from "next/navigation";
+import { useApi } from "@/hooks/use-api";
+import type { DatabaseResponse } from "@/types/api";
+
+export default function SettingsPage() {
+ const params = useParams();
+ const dbId = params.dbId as string;
const id = parseInt(dbId, 10);
- let database;
- let error;
+ const [database, setDatabase] = useState(null);
+ const [error, setError] = useState();
+ const [loading, setLoading] = useState(true);
+ const api = useApi();
+
+
+ useEffect(() => {
+ async function fetchDatabase() {
+ try {
+ const data = await api.getDatabase(id);
+ setDatabase(data);
+ } catch (e) {
+ const errorMessage = e instanceof Error ? e.message : "Failed to load database settings";
+ setError(errorMessage);
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ fetchDatabase();
+ }, [id, api]);
- try {
- database = await getDatabase(id);
- } catch (e) {
- error = e instanceof Error ? e.message : "Failed to load database settings";
+ if (loading) {
+ return (
+
+ );
}
if (error || !database) {
diff --git a/frontend/src/app/dashboard/databases/page.tsx b/frontend/src/app/dashboard/databases/page.tsx
index d8c5a4d..8879f1d 100644
--- a/frontend/src/app/dashboard/databases/page.tsx
+++ b/frontend/src/app/dashboard/databases/page.tsx
@@ -1,17 +1,51 @@
+"use client";
+
+import { useEffect, useState } from "react";
import DatabasesView from "@/modules/dashboard/Databases";
-import { getDatabases } from "@/lib/api";
+import { useApi } from "@/hooks/use-api";
+import { useAuth } from "@/hooks/use-auth";
import type { DatabaseResponse } from "@/types/api";
-export default async function DatabasesPage() {
- let databases: DatabaseResponse[] = [];
- let error: string | undefined;
+export default function DatabasesPage() {
+ const [databases, setDatabases] = useState([]);
+ const [error, setError] = useState();
+ const [loading, setLoading] = useState(true);
+ const api = useApi();
+ const { isLoading: authLoading, isAuthenticated } = useAuth();
+
+ useEffect(() => {
+ if (authLoading) {
+ return;
+ }
+
+ if (!isAuthenticated) {
+ setError("Missing authentication token");
+ setLoading(false);
+ return;
+ }
+
+ async function fetchDatabases() {
+ try {
+ const data = await api.getDatabases();
+ setDatabases(data);
+ } catch (e) {
+ const errorMessage = e instanceof Error ? e.message : "Failed to load databases";
+ setError(errorMessage);
+ console.error("Error fetching databases:", e);
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ fetchDatabases();
+ }, [api, authLoading, isAuthenticated]);
- try {
- databases = await getDatabases();
- } catch (e) {
- error = e instanceof Error ? e.message : "Failed to load databases";
- console.error("Error fetching databases:", e);
- databases = [];
+ if (loading) {
+ return (
+
+ );
}
return ;
diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx
index be2e6b9..c65e654 100644
--- a/frontend/src/app/dashboard/page.tsx
+++ b/frontend/src/app/dashboard/page.tsx
@@ -1,17 +1,51 @@
+"use client";
+
+import { useEffect, useState } from "react";
import DashboardHome from "@/modules/dashboard/index";
-import { getDatabases } from "@/lib/api";
+import { useApi } from "@/hooks/use-api";
+import { useAuth } from "@/hooks/use-auth";
import type { DatabaseResponse } from "@/types/api";
-export default async function DashboardPage() {
- let databases: DatabaseResponse[] = [];
- let error: string | undefined;
+export default function DashboardPage() {
+ const [databases, setDatabases] = useState([]);
+ const [error, setError] = useState();
+ const [loading, setLoading] = useState(true);
+ const api = useApi();
+ const { isLoading: authLoading, isAuthenticated } = useAuth();
+
+ useEffect(() => {
+ if (authLoading) {
+ return;
+ }
+
+ if (!isAuthenticated) {
+ setError("Missing authentication token");
+ setLoading(false);
+ return;
+ }
+
+ async function fetchDatabases() {
+ try {
+ const data = await api.getDatabases();
+ setDatabases(data);
+ } catch (e) {
+ const errorMessage = e instanceof Error ? e.message : "Failed to load databases";
+ setError(errorMessage);
+ console.error("Error fetching databases:", e);
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ fetchDatabases();
+ }, [api, authLoading, isAuthenticated]);
- try {
- databases = await getDatabases();
- } catch (e) {
- error = e instanceof Error ? e.message : "Failed to load databases";
- console.error("Error fetching databases:", e);
- databases = [];
+ if (loading) {
+ return (
+
+ );
}
return ;
diff --git a/frontend/src/app/dashboard/search/page.tsx b/frontend/src/app/dashboard/search/page.tsx
new file mode 100644
index 0000000..a9fe945
--- /dev/null
+++ b/frontend/src/app/dashboard/search/page.tsx
@@ -0,0 +1,5 @@
+import ChatSearch from "@/components/chat/ChatSearch";
+
+export default function DashboardSearchPage() {
+ return ;
+}
diff --git a/frontend/src/components/chat/BookmarksPage.tsx b/frontend/src/components/chat/BookmarksPage.tsx
new file mode 100644
index 0000000..01e1254
--- /dev/null
+++ b/frontend/src/components/chat/BookmarksPage.tsx
@@ -0,0 +1,62 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { useRouter } from "next/navigation";
+import { Trash2 } from "lucide-react";
+import { useChat } from "@/hooks/use-chat";
+
+export default function BookmarksPage() {
+ const router = useRouter();
+ const { listBookmarks, deleteBookmark } = useChat();
+ const [bookmarks, setBookmarks] = useState>>({});
+
+ useEffect(() => {
+ async function load() {
+ const data = await listBookmarks();
+ setBookmarks(data);
+ }
+ load();
+ }, [listBookmarks]);
+
+ async function remove(id: number) {
+ await deleteBookmark(id);
+ const data = await listBookmarks();
+ setBookmarks(data);
+ }
+
+ const days = Object.keys(bookmarks);
+
+ return (
+
+
+
+ {days.length === 0 ? (
+
No bookmarks yet.
+ ) : (
+ days.map((day) => (
+
+ {day}
+
+ {bookmarks[day].map((item) => (
+
+
+
+
+ ))}
+
+
+ ))
+ )}
+
+ );
+}
diff --git a/frontend/src/components/chat/ChatSearch.tsx b/frontend/src/components/chat/ChatSearch.tsx
new file mode 100644
index 0000000..b25948c
--- /dev/null
+++ b/frontend/src/components/chat/ChatSearch.tsx
@@ -0,0 +1,60 @@
+"use client";
+
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import { Search } from "lucide-react";
+import { useChat } from "@/hooks/use-chat";
+
+export default function ChatSearch() {
+ const router = useRouter();
+ const { search } = useChat();
+ const [q, setQ] = useState("");
+ const [results, setResults] = useState>([]);
+
+ async function runSearch() {
+ if (!q.trim()) {
+ setResults([]);
+ return;
+ }
+ const data = await search(q.trim());
+ setResults(data);
+ }
+
+ return (
+
+
+
+
+
+
+ setQ(e.target.value)}
+ onKeyDown={(e) => e.key === "Enter" && runSearch()}
+ placeholder="Search for SQL, schema, or analysis..."
+ className="w-full bg-transparent outline-none text-sm text-[#f0f0f0] placeholder:text-[#666666]"
+ />
+
+
+
+
+
+ {results.map((result) => (
+
+ ))}
+
+
+ );
+}
diff --git a/frontend/src/components/database/DatabaseUploadModal.tsx b/frontend/src/components/database/DatabaseUploadModal.tsx
index f5b8837..132eab2 100644
--- a/frontend/src/components/database/DatabaseUploadModal.tsx
+++ b/frontend/src/components/database/DatabaseUploadModal.tsx
@@ -1,10 +1,16 @@
"use client";
import { useState } from "react";
-import { X, Upload, Loader2 } from "lucide-react";
+import { Upload, Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useApi } from "@/hooks/use-api";
import type { DatabaseUploadResponse } from "@/types/api";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
interface DatabaseUploadModalProps {
isOpen: boolean;
@@ -24,8 +30,6 @@ export default function DatabaseUploadModal({
const [uploading, setUploading] = useState(false);
const [error, setError] = useState(null);
- if (!isOpen) return null;
-
const handleFileChange = (e: React.ChangeEvent) => {
const selectedFile = e.target.files?.[0];
if (selectedFile) {
@@ -85,21 +89,13 @@ export default function DatabaseUploadModal({
};
return (
-
-
- {/* Header */}
-
-
+
-
-
+
+
-
-
+
+
);
}
diff --git a/frontend/src/components/query/QueryInterface.tsx b/frontend/src/components/query/QueryInterface.tsx
index c9b9cbc..f262ff9 100644
--- a/frontend/src/components/query/QueryInterface.tsx
+++ b/frontend/src/components/query/QueryInterface.tsx
@@ -144,6 +144,16 @@ export default function QueryInterface({ databases, preselectedDatabaseId }: Que
)}
+
+ {!selectedDatabaseId && availableDatabases.length === 0 && (
+