Loading projects...
;
return (
Projects
-
Every outcome has a home.
+
Plan and track your work.
-
-
- {error && (
-
-
{error}
-
+ {projects.length === 0 ?
No projects yet.
: (
+
+ {projects.map((p) => (
+
+
+
+ {p.name}
+
+
+
+ {domainMap.get(p.domain) || p.domain}
+ {p.status && {p.status}}
+
+
+
+ ))}
)}
-
- {/* Active projects */}
- {activeProjects.length > 0 && (
-
- Active Projects
-
- {activeProjects.map((project) => (
-
- ))}
-
-
- )}
-
- {/* Paused projects */}
- {pausedProjects.length > 0 && (
-
- Paused Projects
-
- {pausedProjects.map((project) => (
-
- ))}
-
-
- )}
-
- {/* Archived projects */}
- {archivedProjects.length > 0 && (
-
- Archived Projects
-
- {archivedProjects.map((project) => (
-
- ))}
-
-
- )}
-
- {projects.length === 0 && (
-
-
-
- No projects yet
-
- Create your first project to get started
-
-
-
-
- )}
-
+
!o && setDeleteId(null)}>
+
+
+ Delete project?
+ This cannot be undone.
+
+
+ Cancel
+ deleteId && handleDelete(deleteId)} disabled={deleting}>{deleting ? "Deleting..." : "Delete"}
+
+
+
);
}
-
-function ProjectCard({ project }: { project: Project }) {
- return (
-
-
-
-
-
-
{project.name}
- {project.description && (
-
- {project.description}
-
- )}
-
-
- {project.status}
-
-
-
-
- {/* Progress */}
-
-
- Progress
- {project.progress}%
-
-
-
-
- {/* Task count */}
-
- Tasks
-
- {project.completed_count} / {project.task_count}
-
-
-
- {/* Domain and due date */}
-
- {project.domain}
- {project.due_date && (
-
- Due: {new Date(project.due_date).toLocaleDateString()}
-
- )}
-
-
-
-
- );
-}
diff --git a/apps/web/app/(dashboard)/tasks/page.tsx b/apps/web/app/(dashboard)/tasks/page.tsx
index 1662103..018e7ba 100644
--- a/apps/web/app/(dashboard)/tasks/page.tsx
+++ b/apps/web/app/(dashboard)/tasks/page.tsx
@@ -1,72 +1,41 @@
-'use client';
+"use client";
-import { useEffect, useState } from 'react';
-import { LayoutGrid, List, Plus } from 'lucide-react';
-import { Button } from '@/components/ui/button';
-import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
-import { TasksKanbanView } from '@/components/tasks/tasks-kanban-view';
-import { TasksListView } from '@/components/tasks/tasks-list-view';
-import { CreateItemDialog } from '@/components/create-item-dialog';
+import { useState } from "react";
+import { LayoutGrid, List, Plus } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { TasksKanbanView } from "@/components/tasks/tasks-kanban-view";
+import { TasksListView } from "@/components/tasks/tasks-list-view";
+import { CreateItemDialog } from "@/components/create-item-dialog";
+import { useCreateDialogStore } from "@/lib/stores/use-create-dialog-store";
export default function TasksPage() {
- const [view, setView] = useState<'kanban' | 'list'>('kanban');
- const [createOpen, setCreateOpen] = useState(false);
+ const [view, setView] = useState<"kanban" | "list">("kanban");
const [refreshKey, setRefreshKey] = useState(0);
-
- useEffect(() => {
- if (new URLSearchParams(window.location.search).get('new') === 'true') setCreateOpen(true);
- }, []);
-
- function handleCreateOpenChange(open: boolean) {
- setCreateOpen(open);
- if (!open && new URLSearchParams(window.location.search).get('new') === 'true') {
- window.history.replaceState(null, '', '/tasks');
- }
- }
+ const { open, openCreate, closeCreate } = useCreateDialogStore();
return (
Tasks
-
- Move work forward without losing the thread.
-
+
Move work forward without losing the thread.
-
-
- {view === 'kanban' ? (
-
- ) : (
-
- )}
-
setRefreshKey((key) => key + 1)}
- />
+ {view === "kanban" ? : }
+ (o ? openCreate("task") : closeCreate())} onCreated={() => setRefreshKey((k) => k + 1)} />
);
}
diff --git a/apps/web/app/api/mcp/route.ts b/apps/web/app/api/mcp/route.ts
index 92f459e..f235bb5 100644
--- a/apps/web/app/api/mcp/route.ts
+++ b/apps/web/app/api/mcp/route.ts
@@ -43,13 +43,15 @@ export async function GET(request: NextRequest) {
await server.connect(transport);
- // Store transport for POST requests
+ // Handle the request first — sessionId is set during handleRequest
+ const response = await transport.handleRequest(request);
+
+ // Store transport AFTER handleRequest sets the session ID
if (transport.sessionId) {
transports.set(transport.sessionId, transport);
}
- // Handle the request
- return transport.handleRequest(request);
+ return response;
}
export async function POST(request: NextRequest) {
@@ -84,12 +86,15 @@ export async function POST(request: NextRequest) {
await server.connect(transport);
- // Store transport for subsequent requests
+ // Handle the request first — sessionId is set during handleRequest
+ const response = await transport.handleRequest(request);
+
+ // Store transport AFTER handleRequest sets the session ID
if (transport.sessionId) {
transports.set(transport.sessionId, transport);
}
- return transport.handleRequest(request);
+ return response;
}
export async function DELETE(request: NextRequest) {
@@ -124,4 +129,4 @@ export async function DELETE(request: NextRequest) {
transports.delete(sessionId);
return response;
-}
+}
\ No newline at end of file
diff --git a/apps/web/components/create-item-dialog.tsx b/apps/web/components/create-item-dialog.tsx
index c7c817a..dc30594 100644
--- a/apps/web/components/create-item-dialog.tsx
+++ b/apps/web/components/create-item-dialog.tsx
@@ -1,6 +1,6 @@
'use client';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
import { Button } from '@/components/ui/button';
import {
Dialog,
@@ -12,6 +12,13 @@ import {
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
type ItemType = 'task' | 'project' | 'habit';
@@ -21,6 +28,12 @@ const labels = {
habit: { title: 'New habit', field: 'Habit name' },
} as const;
+interface Domain {
+ id: string;
+ name: string;
+ color: string;
+}
+
export function CreateItemDialog({
type,
open,
@@ -33,11 +46,29 @@ export function CreateItemDialog({
onCreated: () => void;
}) {
const [name, setName] = useState('');
- const [domain, setDomain] = useState('General');
+ const [domain, setDomain] = useState('');
+ const [domains, setDomains] = useState
([]);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState('');
const copy = labels[type];
+ useEffect(() => {
+ if (open) {
+ fetch('/api/domains?sort=sort_order')
+ .then((res) => res.json())
+ .then((data) => {
+ const items = data.items || [];
+ setDomains(items);
+ if (items.length > 0 && !domain) {
+ setDomain(items[0].id);
+ }
+ })
+ .catch(() => {
+ setDomains([]);
+ });
+ }
+ }, [open]); // eslint-disable-line react-hooks/exhaustive-deps
+
async function handleSubmit(event: React.FormEvent) {
event.preventDefault();
setSubmitting(true);
@@ -100,19 +131,35 @@ export function CreateItemDialog({