merge: fix/ux-leaf-f-polish into integration/ux-28-gaps (resolve OnboardingFlow+DispatchPanel in layout)

This commit is contained in:
2026-07-29 20:34:52 +00:00
11 changed files with 698 additions and 4 deletions
+12 -3
View File
@@ -6,7 +6,7 @@ import { useDashboardStore } from '@/lib/stores/use-dashboard-store';
import { WidgetErrorBoundary } from '@/components/widget-error-boundary';
import { Button } from '@/components/ui/button';
import { Settings2, LayoutGrid } from 'lucide-react';
import { useRouter } from 'next/navigation';
import { useRouter, useSearchParams } from 'next/navigation';
// Lazy load react-grid-layout (client-only, ~45KB)
const ResponsiveGridLayout = dynamic(
@@ -68,12 +68,14 @@ const widgetLabels: Record<string, string> = {
'quick-capture': 'Quick Capture',
};
export default function DashboardPage() {
function DashboardPage() {
const { widgets, setWidgets, addWidget, removeWidget } = useDashboardStore();
const [layoutAnnouncement, setLayoutAnnouncement] = React.useState('');
const [editMode, setEditMode] = React.useState(false);
const [showConfig, setShowConfig] = React.useState(false);
const router = useRouter();
const searchParams = useSearchParams();
const domainFilter = searchParams.get('domain');
const layout = widgets.map((w) => ({
i: w.id,
@@ -113,7 +115,7 @@ export default function DashboardPage() {
<div className="mb-6 flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold">Dashboard</h1>
<p className="mt-1 text-muted-foreground">Your day, at a glance.</p>
<p className="mt-1 text-muted-foreground">Your day, at a glance.{domainFilter ? " (Filtered: " + domainFilter + ")" : ""}</p>
</div>
<div className="flex items-center gap-2">
<Button
@@ -201,3 +203,10 @@ export default function DashboardPage() {
</div>
);
}
export default function DashboardPageWrapper() {
return (
<Suspense fallback={<div className="py-12 text-center text-muted-foreground">Loading dashboard...</div>}>
<DashboardPage />
</Suspense>
);
}
+6
View File
@@ -24,6 +24,7 @@ import { HabitCreateDialog } from "@/components/habits/habit-create-dialog";
import { HabitEditDialog } from "@/components/habits/habit-edit-dialog";
import { HabitCompletionDialog } from "@/components/habits/habit-completion-dialog";
import { HabitCalendarHeatmap } from "@/components/habits/habit-calendar-heatmap";
import { HabitAnalytics } from "@/components/habits/habit-analytics";
import { toast } from "sonner";
interface Habit {
@@ -247,6 +248,11 @@ export default function HabitsPage() {
</div>
)}
{/* Analytics */}
<div className="mt-4">
<HabitAnalytics domainId={domainId || ""} habits={habits} />
</div>
<HabitCreateDialog
open={createOpen}
onOpenChange={setCreateOpen}
+3 -1
View File
@@ -5,6 +5,7 @@ import { KeyboardShortcutsProvider } from '@/components/keyboard-shortcuts-provi
import { WebVitalsTracker } from '@/components/web-vitals-tracker';
import { MobileBottomNav } from '@/components/mobile-bottom-nav';
import { DispatchPanel } from '@/components/agents/dispatch-panel';
import { OnboardingFlow } from '@/components/onboarding/onboarding-flow';
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
@@ -18,7 +19,7 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
<Sidebar />
<div className="flex flex-1 flex-col pb-16 md:pb-0">
<TopBar />
<main id="main-content" className="flex-1 overflow-auto p-6" tabIndex={-1}>
<main id="main-content" className="flex-1 overflow-auto p-4 md:p-6" tabIndex={-1}>
{children}
</main>
</div>
@@ -32,6 +33,7 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
triggerClassName="h-12 w-12 rounded-full shadow-lg md:h-auto md:w-auto md:rounded-md md:px-4 md:py-2"
/>
</div>
<OnboardingFlow />
<div className="sr-only" aria-live="polite" aria-atomic="true" id="a11y-announcer" />
</KeyboardShortcutsProvider>
);
+26
View File
@@ -5,6 +5,7 @@ import { Plus, FileText, Link2, GitBranch, Trash2, Pin, Archive, Search, PinOff,
import dynamic from 'next/dynamic';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { NoteTemplates } from '@/components/notes/note-templates';
import { Card } from '@/components/ui/card';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Badge } from '@/components/ui/badge';
@@ -162,6 +163,28 @@ export default function NotesPage() {
}
}
async function createNoteWithContent(content: string) {
if (!domainId) return;
try {
const response = await fetch("/api/domains/" + domainId + "/notes", {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: 'Untitled note',
content,
}),
});
if (!response.ok) throw new Error('Unable to create note.');
const newNote = await response.json();
setNotes((current) => [newNote, ...current]);
setSelectedNote(newNote);
toast.success('Note created from template');
} catch (error) {
console.error('Failed to create note:', error);
toast.error('Unable to create note');
}
}
async function createNote() {
if (!domainId) return;
try {
@@ -339,6 +362,9 @@ export default function NotesPage() {
))}
</select>
)}
<NoteTemplates onCreateFromTemplate={(content) => {
createNoteWithContent(content);
}} />
<Button onClick={createNote}>
<Plus className="mr-2 h-4 w-4" />
New note
@@ -23,6 +23,7 @@ import {
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { SectionDialog } from "@/components/projects/section-dialog";
import { ProjectTimeline } from "@/components/projects/project-timeline";
import Link from "next/link";
import { toast } from "sonner";
@@ -331,6 +332,9 @@ export default function ProjectDetailPage() {
</div>
</div>
{/* Timeline */}
<ProjectTimeline sections={project.sections} projectTargetDate={project.targetDate} />
<SectionDialog
open={sectionDialogOpen}
onOpenChange={setSectionDialogOpen}