diff --git a/backend/src/index.ts b/backend/src/index.ts
index 4949b53..0fa4803 100644
--- a/backend/src/index.ts
+++ b/backend/src/index.ts
@@ -52,7 +52,7 @@ ensureAdmin().catch(console.error);
const app = new Elysia()
.use(
cors({
- origin: ["https://queue.donovankelly.xyz", "http://localhost:5173"],
+ origin: ["https://dash.donovankelly.xyz", "https://queue.donovankelly.xyz", "http://localhost:5173"],
credentials: true,
allowedHeaders: ["Content-Type", "Authorization", "Cookie"],
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
diff --git a/backend/src/lib/auth.ts b/backend/src/lib/auth.ts
index a8ae69d..1b30da7 100644
--- a/backend/src/lib/auth.ts
+++ b/backend/src/lib/auth.ts
@@ -25,6 +25,7 @@ export const auth = betterAuth({
},
},
trustedOrigins: [
+ "https://dash.donovankelly.xyz",
"https://queue.donovankelly.xyz",
"http://localhost:5173",
],
diff --git a/docker-compose.dokploy.yml b/docker-compose.dokploy.yml
index 3a67be2..bcfa672 100644
--- a/docker-compose.dokploy.yml
+++ b/docker-compose.dokploy.yml
@@ -21,7 +21,7 @@ services:
DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
API_BEARER_TOKEN: ${API_BEARER_TOKEN}
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
- BETTER_AUTH_URL: https://queue.donovankelly.xyz
+ BETTER_AUTH_URL: https://dash.donovankelly.xyz
COOKIE_DOMAIN: .donovankelly.xyz
CLAWDBOT_HOOK_URL: ${CLAWDBOT_HOOK_URL:-https://hooks.hammer.donovankelly.xyz/hooks/agent}
CLAWDBOT_HOOK_TOKEN: ${CLAWDBOT_HOOK_TOKEN}
diff --git a/frontend/bun.lock b/frontend/bun.lock
index 02e1dbd..78c2118 100644
--- a/frontend/bun.lock
+++ b/frontend/bun.lock
@@ -9,6 +9,7 @@
"better-auth": "^1.4.17",
"react": "^19.2.0",
"react-dom": "^19.2.0",
+ "react-router-dom": "^7.13.0",
"tailwindcss": "^4.1.18",
},
"devDependencies": {
@@ -326,6 +327,8 @@
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
+ "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
+
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
@@ -508,6 +511,10 @@
"react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
+ "react-router": ["react-router@7.13.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw=="],
+
+ "react-router-dom": ["react-router-dom@7.13.0", "", { "dependencies": { "react-router": "7.13.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g=="],
+
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
"rollup": ["rollup@4.57.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.0", "@rollup/rollup-android-arm64": "4.57.0", "@rollup/rollup-darwin-arm64": "4.57.0", "@rollup/rollup-darwin-x64": "4.57.0", "@rollup/rollup-freebsd-arm64": "4.57.0", "@rollup/rollup-freebsd-x64": "4.57.0", "@rollup/rollup-linux-arm-gnueabihf": "4.57.0", "@rollup/rollup-linux-arm-musleabihf": "4.57.0", "@rollup/rollup-linux-arm64-gnu": "4.57.0", "@rollup/rollup-linux-arm64-musl": "4.57.0", "@rollup/rollup-linux-loong64-gnu": "4.57.0", "@rollup/rollup-linux-loong64-musl": "4.57.0", "@rollup/rollup-linux-ppc64-gnu": "4.57.0", "@rollup/rollup-linux-ppc64-musl": "4.57.0", "@rollup/rollup-linux-riscv64-gnu": "4.57.0", "@rollup/rollup-linux-riscv64-musl": "4.57.0", "@rollup/rollup-linux-s390x-gnu": "4.57.0", "@rollup/rollup-linux-x64-gnu": "4.57.0", "@rollup/rollup-linux-x64-musl": "4.57.0", "@rollup/rollup-openbsd-x64": "4.57.0", "@rollup/rollup-openharmony-arm64": "4.57.0", "@rollup/rollup-win32-arm64-msvc": "4.57.0", "@rollup/rollup-win32-ia32-msvc": "4.57.0", "@rollup/rollup-win32-x64-gnu": "4.57.0", "@rollup/rollup-win32-x64-msvc": "4.57.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA=="],
diff --git a/frontend/index.html b/frontend/index.html
index 361f29f..1839882 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -4,7 +4,7 @@
-
Hammer Queue
+ Hammer Dashboard
diff --git a/frontend/package.json b/frontend/package.json
index 10072f4..b866ffa 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -14,6 +14,7 @@
"better-auth": "^1.4.17",
"react": "^19.2.0",
"react-dom": "^19.2.0",
+ "react-router-dom": "^7.13.0",
"tailwindcss": "^4.1.18"
},
"devDependencies": {
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index b6aa7c3..2a0f43f 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,254 +1,23 @@
-import { useState, useMemo } from "react";
-import { useTasks } from "./hooks/useTasks";
-import { useCurrentUser } from "./hooks/useCurrentUser";
-import { TaskCard } from "./components/TaskCard";
-import { TaskDetailPanel } from "./components/TaskDetailPanel";
-import { CreateTaskModal } from "./components/CreateTaskModal";
+import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
+import { DashboardLayout } from "./components/DashboardLayout";
+import { QueuePage } from "./pages/QueuePage";
+import { ChatPage } from "./pages/ChatPage";
import { AdminPage } from "./components/AdminPage";
import { LoginPage } from "./components/LoginPage";
-import { useSession, signOut } from "./lib/auth-client";
-import { updateTask, reorderTasks, createTask } from "./lib/api";
-import type { TaskStatus } from "./lib/types";
-
-function Dashboard() {
- const { tasks, loading, error, refresh } = useTasks(5000);
- const { user, isAdmin, isAuthenticated } = useCurrentUser();
- const [showCreate, setShowCreate] = useState(false);
- const [showCompleted, setShowCompleted] = useState(false);
- const [selectedTask, setSelectedTask] = useState(null);
- const [showAdmin, setShowAdmin] = useState(false);
-
- const selectedTaskData = useMemo(() => {
- if (!selectedTask) return null;
- return tasks.find((t) => t.id === selectedTask) || null;
- }, [tasks, selectedTask]);
-
- const activeTasks = useMemo(() => tasks.filter((t) => t.status === "active"), [tasks]);
- const queuedTasks = useMemo(() => tasks.filter((t) => t.status === "queued"), [tasks]);
- const blockedTasks = useMemo(() => tasks.filter((t) => t.status === "blocked"), [tasks]);
- const completedTasks = useMemo(
- () => tasks.filter((t) => t.status === "completed" || t.status === "cancelled"),
- [tasks]
- );
-
- const handleStatusChange = async (id: string, status: TaskStatus) => {
- try {
- await updateTask(id, { status });
- refresh();
- } catch (e) {
- alert("Failed to update task.");
- }
- };
-
- const handleMoveUp = async (index: number) => {
- if (index === 0) return;
- const ids = queuedTasks.map((t) => t.id);
- [ids[index - 1], ids[index]] = [ids[index], ids[index - 1]];
- await reorderTasks(ids);
- refresh();
- };
-
- const handleMoveDown = async (index: number) => {
- if (index >= queuedTasks.length - 1) return;
- const ids = queuedTasks.map((t) => t.id);
- [ids[index], ids[index + 1]] = [ids[index + 1], ids[index]];
- await reorderTasks(ids);
- refresh();
- };
-
- const handleCreate = async (task: {
- title: string;
- description?: string;
- source?: string;
- priority?: string;
- }) => {
- await createTask(task);
- refresh();
- };
-
- const handleLogout = async () => {
- await signOut();
- window.location.reload();
- };
-
- if (showAdmin) {
- return setShowAdmin(false)} />;
- }
+import { useSession } from "./lib/auth-client";
+function AuthenticatedApp() {
return (
-
- {/* Header */}
-
-
-
setShowCreate(false)}
- onCreate={handleCreate}
- />
-
-
- {loading && (
- Loading tasks...
- )}
- {error && (
-
- {error}
-
- )}
-
- {/* Active Task */}
-
-
- โก Currently Working On
-
- {activeTasks.length === 0 ? (
-
- No active task โ Hammer is idle
-
- ) : (
-
- {activeTasks.map((task) => (
- setSelectedTask(task.id)}
- />
- ))}
-
- )}
-
-
- {/* Blocked */}
- {blockedTasks.length > 0 && (
-
-
- ๐ซ Blocked ({blockedTasks.length})
-
-
- {blockedTasks.map((task) => (
- setSelectedTask(task.id)}
- />
- ))}
-
-
- )}
-
- {/* Queue */}
-
-
- ๐ Queue ({queuedTasks.length})
-
- {queuedTasks.length === 0 ? (
-
- Queue is empty
-
- ) : (
-
- {queuedTasks.map((task, i) => (
- handleMoveUp(i)}
- onMoveDown={() => handleMoveDown(i)}
- isFirst={i === 0}
- isLast={i === queuedTasks.length - 1}
- onClick={() => setSelectedTask(task.id)}
- />
- ))}
-
- )}
-
-
- {/* Completed */}
-
-
- {showCompleted && (
-
- {completedTasks.map((task) => (
- setSelectedTask(task.id)}
- />
- ))}
-
- )}
-
-
-
- {/* Task Detail Panel */}
- {selectedTaskData && (
- setSelectedTask(null)}
- onStatusChange={(id, status) => {
- handleStatusChange(id, status);
- setSelectedTask(null);
- }}
- onTaskUpdated={refresh}
- hasToken={isAuthenticated}
- token=""
- />
- )}
-
- {/* Footer */}
-
-
+
+
+ }>
+ } />
+ } />
+ } />
+ } />
+
+
+
);
}
@@ -267,7 +36,7 @@ function App() {
return window.location.reload()} />;
}
- return ;
+ return ;
}
export default App;
diff --git a/frontend/src/components/AdminPage.tsx b/frontend/src/components/AdminPage.tsx
index 78ed05b..6aded57 100644
--- a/frontend/src/components/AdminPage.tsx
+++ b/frontend/src/components/AdminPage.tsx
@@ -1,4 +1,5 @@
import { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
import { fetchUsers, updateUserRole, deleteUser } from "../lib/api";
interface User {
@@ -9,7 +10,8 @@ interface User {
createdAt: string;
}
-export function AdminPage({ onBack }: { onBack: () => void }) {
+export function AdminPage() {
+ const navigate = useNavigate();
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
@@ -56,7 +58,7 @@ export function AdminPage({ onBack }: { onBack: () => void }) {
Manage users and roles