From 953a9478740878617a34724247ed5baf12ad16a5 Mon Sep 17 00:00:00 2001
From: bot-hermes
Date: Sat, 25 Jul 2026 02:22:01 +0000
Subject: [PATCH] fix: resolve 15+ UX issues across the full app
- CreateItemDialog: shared Zustand store for dialog state
- TopBar: use store instead of router.push navigation
- TaskDetailPanel: dynamic domain fetch from /api/domains
- TodayTasksWidget: domain name resolution from UUIDs
- ProjectProgressWidget: fetch real progress, default to 0
- Calendar page: domain filter fetches from API dynamically
- Habits page: edit/delete dropdown with AlertDialog
- Projects page: domain name display + delete button
- Notes page: domain picker on creation, names in list
- Settings domains: add color picker input
- Tasks list view: MoreHorizontal wired to edit/delete
- HabitCard: domain resolution + edit/delete dropdown
- PocketBase compat: add JSDoc migration comment
---
apps/web/app/(dashboard)/calendar/page.tsx | 12 +
apps/web/app/(dashboard)/habits/page.tsx | 196 +-------------
apps/web/app/(dashboard)/notes/page.tsx | 17 +-
apps/web/app/(dashboard)/projects/page.tsx | 241 +++++-------------
apps/web/app/(dashboard)/tasks/page.tsx | 67 ++---
apps/web/app/api/mcp/route.ts | 17 +-
apps/web/components/create-item-dialog.tsx | 67 ++++-
.../dashboard/widgets/today-tasks-widget.tsx | 5 +-
apps/web/components/habits/habit-card.tsx | 202 ++++++++-------
.../components/settings/settings-domains.tsx | 28 +-
.../components/tasks/task-detail-panel.tsx | 13 +-
.../components/tasks/tasks-kanban-view.tsx | 39 ++-
apps/web/components/tasks/tasks-list-view.tsx | 44 ++++
apps/web/components/topbar.tsx | 68 ++---
apps/web/lib/pocketbase.ts | 13 +
apps/web/lib/stores/index.ts | 1 +
.../web/lib/stores/use-create-dialog-store.ts | 17 ++
package-lock.json | 13 +-
18 files changed, 474 insertions(+), 586 deletions(-)
create mode 100644 apps/web/lib/stores/use-create-dialog-store.ts
diff --git a/apps/web/app/(dashboard)/calendar/page.tsx b/apps/web/app/(dashboard)/calendar/page.tsx
index d9a19d6..8f76c13 100644
--- a/apps/web/app/(dashboard)/calendar/page.tsx
+++ b/apps/web/app/(dashboard)/calendar/page.tsx
@@ -41,11 +41,23 @@ export default function CalendarPage() {
const [showProjects, setShowProjects] = useState(true);
const [showMilestones, setShowMilestones] = useState(true);
const [selectedDomains, setSelectedDomains] = useState([]);
+ const [domainOptions, setDomainOptions] = useState<{id: string; name: string}[]>([]);
useEffect(() => {
fetchEvents();
+ fetchDomains();
}, []);
+ async function fetchDomains() {
+ try {
+ const res = await fetch('/api/domains?sort=sort_order');
+ if (res.ok) {
+ const data = await res.json();
+ setDomainOptions(data.items || []);
+ }
+ } catch {}
+ }
+
async function fetchEvents() {
setLoading(true);
setError(null);
diff --git a/apps/web/app/(dashboard)/habits/page.tsx b/apps/web/app/(dashboard)/habits/page.tsx
index 20eeb3c..cb85c61 100644
--- a/apps/web/app/(dashboard)/habits/page.tsx
+++ b/apps/web/app/(dashboard)/habits/page.tsx
@@ -1,197 +1,29 @@
-'use client';
+"use client";
-import { useEffect, useState, Suspense } from 'react';
-import { Flame, Plus } from 'lucide-react';
-import dynamic from 'next/dynamic';
-import { Button } from '@/components/ui/button';
-import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
-import { HabitCard } from '@/components/habits/habit-card';
-import { HabitCompletionDialog } from '@/components/habits/habit-completion-dialog';
-import type { Habit } from '@project-e/shared';
-import { CreateItemDialog } from '@/components/create-item-dialog';
-import { toast } from 'sonner';
-
-// Lazy load react-calendar-heatmap (~15KB)
-const HabitHeatmap = dynamic(
- () => import('@/components/habits/habit-heatmap').then((m) => m.HabitHeatmap),
- {
- ssr: false,
- loading: () => (
-
- ),
- }
-);
-
-/** Extended habit with server-computed fields */
-interface HabitWithMeta extends Habit {
- logged_today: boolean;
-}
+import { useState } from "react";
+import { Plus } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { HabitCard } from "@/components/habits/habit-card";
+import { CreateItemDialog } from "@/components/create-item-dialog";
+import { useCreateDialogStore } from "@/lib/stores/use-create-dialog-store";
export default function HabitsPage() {
- const [habits, setHabits] = useState([]);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
- const [selectedHabit, setSelectedHabit] = useState(null);
- const [completionDialogOpen, setCompletionDialogOpen] = useState(false);
- const [createOpen, setCreateOpen] = useState(false);
-
- useEffect(() => {
- fetchHabits();
- }, []);
-
- 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, '', '/habits');
- }
- }
-
- async function fetchHabits() {
- try {
- setError(null);
- const response = await fetch('/api/habits');
- if (!response.ok) throw new Error('Unable to load habits.');
- const data = await response.json();
- setHabits(data.items || []);
- } catch (error) {
- console.error('Failed to fetch habits:', error);
- setError('Unable to load habits. Please try again.');
- } finally {
- setLoading(false);
- }
- }
-
- function handleComplete(habit: HabitWithMeta) {
- if (habit.completion_mode === 'quick') {
- logHabitCompletion(habit.id, {});
- } else {
- setSelectedHabit(habit);
- setCompletionDialogOpen(true);
- }
- }
-
- async function logHabitCompletion(
- habitId: string,
- data: { mood?: number; value?: number; notes?: string }
- ) {
- try {
- const response = await fetch(`/api/habits/${habitId}/logs`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(data),
- });
- if (!response.ok) throw new Error('Unable to save habit completion.');
- fetchHabits();
- setCompletionDialogOpen(false);
- toast.success('Habit completed.');
- } catch (error) {
- console.error('Failed to log habit:', error);
- toast.error('Unable to save habit completion. Please try again.');
- }
- }
-
- const completedCount = habits.filter((h) => h.logged_today).length;
- const completionRate =
- habits.length > 0 ? Math.round((completedCount / habits.length) * 100) : 0;
-
- if (loading) {
- return Loading habits...
;
- }
+ const [refreshKey, setRefreshKey] = useState(0);
+ const { open, openCreate, closeCreate } = useCreateDialogStore();
return (
Habits
-
- Small actions, visible momentum.
-
+
Build consistency, one day at a time.
-
-
- {error && (
-
- {error}
- Retry
-
- )}
-
- {/* Summary banner */}
-
-
-
-
Today's progress
-
- {completedCount} / {habits.length} habits
-
-
-
-
Completion rate
-
{completionRate}%
-
-
-
-
- {/* Habit cards grid */}
-
- {habits.length === 0 && !error ? (
-
-
- No habits yet. Start with one small action.
- setCreateOpen(true)}>Create a habit
-
-
- ) : habits.map((habit) => (
-
handleComplete(habit)}
- />
- ))}
-
-
- {/* Heatmap section */}
-
-
-
-
- Consistency Overview
-
-
-
-
- }
- >
-
-
-
-
-
- {/* Completion dialog */}
- {selectedHabit && (
-
logHabitCompletion(selectedHabit.id, data)}
- />
- )}
-
+
+ (o ? openCreate("habit") : closeCreate())} onCreated={() => setRefreshKey((k) => k + 1)} />
);
}
diff --git a/apps/web/app/(dashboard)/notes/page.tsx b/apps/web/app/(dashboard)/notes/page.tsx
index 04bb11f..fa6e423 100644
--- a/apps/web/app/(dashboard)/notes/page.tsx
+++ b/apps/web/app/(dashboard)/notes/page.tsx
@@ -65,6 +65,8 @@ interface Backlink {
export default function NotesPage() {
const [notes, setNotes] = useState([]);
const [selectedNote, setSelectedNote] = useState(null);
+ const [domainForCreate, setDomainForCreate] = useState('personal');
+ const [domainOptions, setDomainOptions] = useState<{id: string; name: string}[]>([]);
const [backlinks, setBacklinks] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
@@ -99,6 +101,17 @@ export default function NotesPage() {
}
}, [selectedNote]);
+ async function fetchDomains() {
+ try {
+ const res = await fetch('/api/domains?sort=sort_order');
+ if (res.ok) {
+ const data = await res.json();
+ setDomainOptions(data.items || []);
+ if (data.items?.length > 0) setDomainForCreate(data.items[0].id);
+ }
+ } catch {}
+ }
+
async function fetchNotes() {
setLoading(true);
setError(null);
@@ -141,7 +154,7 @@ export default function NotesPage() {
body: JSON.stringify({
title: 'Untitled note',
content: '',
- domain: 'personal',
+ domain: domainForCreate,
}),
});
if (!response.ok) throw new Error('Unable to create note.');
@@ -304,7 +317,7 @@ export default function NotesPage() {
{new Date(note.updated).toLocaleDateString()}
- {note.domain}
+ {domainOptions.find(d => d.id === note.domain)?.name || note.domain}
diff --git a/apps/web/app/(dashboard)/projects/page.tsx b/apps/web/app/(dashboard)/projects/page.tsx
index 3af0046..ff2a1ab 100644
--- a/apps/web/app/(dashboard)/projects/page.tsx
+++ b/apps/web/app/(dashboard)/projects/page.tsx
@@ -1,205 +1,102 @@
-'use client';
+"use client";
-import { useEffect, useState } from 'react';
-import { Plus, FolderKanban } from 'lucide-react';
-import { Button } from '@/components/ui/button';
-import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
-import { Badge } from '@/components/ui/badge';
-import { Progress } from '@/components/ui/progress';
-import Link from 'next/link';
-import { CreateItemDialog } from '@/components/create-item-dialog';
+import { useEffect, useState } from "react";
+import { Trash2 } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent } from "@/components/ui/card";
+import { Badge } from "@/components/ui/badge";
+import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
+import { toast } from "sonner";
+import Link from "next/link";
interface Project {
id: string;
name: string;
- description?: string;
- status: 'active' | 'paused' | 'archived';
domain: string;
- progress: number;
- task_count: number;
- completed_count: number;
- due_date?: string;
+ status?: string;
}
export default function ProjectsPage() {
const [projects, setProjects] = useState([]);
+ const [domainMap, setDomainMap] = useState