Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions apps/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#030712" />
<meta name="theme-color" content="#070b0f" />
<meta name="color-scheme" content="dark" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
</head>
<body style="background-color: #030712">
<body style="background-color: #070b0f">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
Expand Down
10 changes: 5 additions & 5 deletions apps/web/src/components/CaptureCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default function CaptureCard({ capture, onEdit, onDelete }: CaptureCardPr
<textarea
value={editedContent}
onChange={(e) => setEditedContent(e.target.value)}
className="w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded text-sm text-gray-200 font-mono leading-relaxed focus:outline-none focus:border-accent-highlight resize-y min-h-[60px]"
className="input-edit font-mono leading-relaxed resize-y min-h-[60px]"
autoFocus
onKeyDown={(e) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
Expand All @@ -65,7 +65,7 @@ export default function CaptureCard({ capture, onEdit, onDelete }: CaptureCardPr
<button
onClick={handleSaveEdit}
disabled={isSaving}
className="flex items-center gap-1 px-3 py-1 bg-green-600 hover:bg-green-700 disabled:bg-green-800 disabled:opacity-50 text-white text-xs rounded transition-colors"
className="btn-save"
>
{isSaving ? (
<Loader2 className="w-3 h-3 animate-spin" />
Expand All @@ -77,7 +77,7 @@ export default function CaptureCard({ capture, onEdit, onDelete }: CaptureCardPr
<button
onClick={handleCancelEdit}
disabled={isSaving}
className="flex items-center gap-1 px-3 py-1 bg-gray-700 hover:bg-gray-600 disabled:bg-gray-800 disabled:opacity-50 text-gray-200 text-xs rounded transition-colors"
className="btn-cancel"
>
<X className="w-3 h-3" />
Cancel
Expand All @@ -101,15 +101,15 @@ export default function CaptureCard({ capture, onEdit, onDelete }: CaptureCardPr
<div className="absolute top-4 right-2 flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
<button
onClick={handleStartEdit}
className="px-2 py-1 hover:bg-gray-700 rounded"
className="icon-btn"
title="Edit capture"
aria-label="Edit capture"
>
<Pencil className="w-3 h-3 text-gray-400" />
</button>
<button
onClick={() => onDelete(capture.id)}
className="text-gray-500 hover:text-red-400 text-sm px-2 py-1 rounded hover:bg-gray-800"
className="text-gray-500 hover:text-red-400 text-sm px-2 py-1 rounded hover:bg-white/[0.06]"
title="Delete"
aria-label="Delete capture"
>
Expand Down
238 changes: 130 additions & 108 deletions apps/web/src/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Link, useLocation } from 'react-router-dom';
import { ReactNode, useState, useEffect } from 'react';
import { useInboxCount } from '../api/queries';
import { PenLine, CalendarDays, Inbox, FileText, ListChecks, Layers, MessageSquare, Cog, Menu, X, Mic, TrendingUp } from 'lucide-react';
import { PenLine, CalendarDays, Inbox, FileText, ListChecks, Layers, MessageSquare, Cog, Menu, X, Mic, TrendingUp, Brain } from 'lucide-react';
import { useQueryClient } from '@tanstack/react-query';

const isElectron = typeof window !== 'undefined' && window.electronAPI?.isElectron;
Expand All @@ -10,20 +10,30 @@ interface LayoutProps {
children: ReactNode;
}

const primaryNavItems = [
{ path: '/', label: 'Capture', icon: PenLine },
{ path: '/today', label: 'Today Sheet', icon: CalendarDays },
{ path: '/weekly-review', label: 'Weekly Review', icon: TrendingUp },
{ path: '/inbox', label: 'Inbox', icon: Inbox },
{ path: '/notes', label: 'Notes', icon: FileText },
{ path: '/todos', label: 'Todos', icon: ListChecks },
];

const toolNavItems = [
{ path: '/templates', label: 'Templates', icon: Layers },
{ path: '/chat', label: 'Chat', icon: MessageSquare },
];

export default function Layout({ children }: LayoutProps) {
const { data: inboxCount = 0 } = useInboxCount();
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const queryClient = useQueryClient();

// Listen for capture created events from Quick Capture window
useEffect(() => {
if (!window.electronAPI?.onCaptureCreated) return;

const unsubscribe = window.electronAPI.onCaptureCreated(() => {
// Invalidate inbox count when a capture is created
queryClient.invalidateQueries({ queryKey: ['inboxCount'] });
});

return unsubscribe;
}, [queryClient]);

Expand All @@ -34,158 +44,171 @@ export default function Layout({ children }: LayoutProps) {
return location.pathname.startsWith(path);
};

const navItems = [
{ path: '/', label: 'Capture', icon: PenLine },
{ path: '/today', label: 'Today Sheet', icon: CalendarDays },
{ path: '/weekly-review', label: 'Weekly Review', icon: TrendingUp },
{ path: '/inbox', label: 'Inbox', icon: Inbox },
{ path: '/notes', label: 'Notes', icon: FileText },
{ path: '/todos', label: 'Todos', icon: ListChecks },
{ path: '/templates', label: 'Templates', icon: Layers },
{ path: '/chat', label: 'Chat', icon: MessageSquare },
{ path: '/settings', label: 'Settings', icon: Cog },
];

const closeDrawer = () => setIsDrawerOpen(false);

const NavLink = ({ item, onClick }: { item: { path: string; label: string; icon: React.ElementType }; onClick?: () => void }) => {
const Icon = item.icon;
const active = isActive(item.path);
return (
<Link
to={item.path}
onClick={onClick}
className={`
flex items-center gap-3 mx-2 px-3 py-2 rounded-lg text-sm font-medium transition-all duration-150
${active
? 'bg-white/[0.08] text-white'
: 'text-gray-500 hover:text-gray-200 hover:bg-white/[0.05]'
}
`}
>
<Icon className={`w-4 h-4 flex-shrink-0 ${active ? 'text-accent' : ''}`} style={active ? { color: '#9b8dd4' } : undefined} />
<span>{item.label}</span>
{item.path === '/inbox' && inboxCount > 0 && (
<span className="ml-auto badge-accent">
{inboxCount}
</span>
)}
</Link>
);
};

const SectionLabel = ({ label }: { label: string }) => (
<p className="px-5 mb-1 mt-5 text-[10px] uppercase tracking-widest text-gray-600 font-semibold select-none">
{label}
</p>
);

return (
<div className="flex h-full bg-gray-950 text-gray-100 overflow-hidden">
<div className="flex h-full text-gray-100 overflow-hidden" style={{ backgroundColor: '#070b0f' }}>
{/* Sidebar - Desktop */}
<aside className="hidden md:block w-64 bg-gray-900 border-r border-gray-800 shadow-2xl flex-shrink-0">
{/* Draggable title bar region for Electron */}
<aside className="hidden md:flex flex-col w-60 flex-shrink-0" style={{ backgroundColor: '#0c1117', borderRight: '1px solid rgba(255,255,255,0.04)' }}>
{/* Electron drag region */}
{isElectron && (
<div
className="h-8 w-full"
className="h-8 w-full flex-shrink-0"
style={{ WebkitAppRegion: 'drag' } as React.CSSProperties}
/>
)}
<div className={`p-6 ${isElectron ? 'pt-2' : ''}`}>
<h1 className="text-2xl font-bold text-gray-100">Mind Melder</h1>
<p className="text-sm text-gray-400 mt-1">Quick Capture & AI Organizer</p>

{/* Branding */}
<div className={`flex items-center gap-2.5 px-4 ${isElectron ? 'py-3' : 'py-5'}`}>
<div className="w-7 h-7 rounded-lg flex items-center justify-center flex-shrink-0" style={{ backgroundColor: 'rgba(114,97,175,0.22)', border: '1px solid rgba(114,97,175,0.28)' }}>
<Brain className="w-4 h-4" style={{ color: '#9b8dd4' }} />
</div>
<span className="text-[15px] font-semibold text-gray-100 tracking-tight">Mind Melder</span>
</div>

<nav className="mt-6">
{navItems.map((item) => {
const Icon = item.icon;
return (
<Link
key={item.path}
to={item.path}
className={`
flex items-center gap-3 px-6 py-3 transition-all
${
isActive(item.path)
? 'bg-gray-800 border-l-4 border-accent text-gray-100'
: 'text-gray-400 hover:text-gray-200 hover:bg-gray-800/50'
}
`}
>
<Icon className="w-5 h-5" />
<span className="font-medium">{item.label}</span>
{item.path === '/inbox' && inboxCount > 0 && (
<span className="badge-accent">
{inboxCount}
</span>
)}
</Link>
);
})}
{/* Primary nav */}
<nav className="flex-1 pb-2 overflow-y-auto">
<SectionLabel label="Workspace" />
<div className="space-y-0.5">
{primaryNavItems.map((item) => (
<NavLink key={item.path} item={item} />
))}
</div>

<SectionLabel label="Tools" />
<div className="space-y-0.5">
{toolNavItems.map((item) => (
<NavLink key={item.path} item={item} />
))}
</div>
</nav>

{/* Bottom: Settings + Record */}
<div className="pb-4 pt-2 space-y-0.5" style={{ borderTop: '1px solid rgba(255,255,255,0.04)' }}>
<NavLink item={{ path: '/settings', label: 'Settings', icon: Cog }} />
{isElectron && (
<button
onClick={() => window.electronAPI?.openRecordingWindow()}
className="flex items-center gap-3 px-6 py-3 transition-all text-gray-400 hover:text-gray-200 hover:bg-gray-800/50 w-full"
className="flex items-center gap-3 mx-2 px-3 py-2 rounded-lg text-sm font-medium transition-all duration-150 text-gray-500 hover:text-gray-200 hover:bg-white/[0.05] w-[calc(100%-16px)]"
>
<Mic className="w-5 h-5" />
<span className="font-medium">Record</span>
<Mic className="w-4 h-4 flex-shrink-0" />
<span>Record</span>
</button>
)}
</nav>
</div>
</aside>

{/* Mobile Header */}
<div className="md:hidden fixed top-0 left-0 right-0 bg-gray-900 border-b border-gray-800 z-50">
<div className="md:hidden fixed top-0 left-0 right-0 z-50" style={{ backgroundColor: '#0c1117', borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
<div
className="flex items-center justify-between px-4 py-3"
style={isElectron ? { WebkitAppRegion: 'drag' } as React.CSSProperties : undefined}
>
<h1 className="text-lg font-bold text-gray-100">Mind Melder</h1>
<div className="flex items-center gap-2">
<div className="w-6 h-6 rounded-md flex items-center justify-center" style={{ backgroundColor: 'rgba(114,97,175,0.22)' }}>
<Brain className="w-3.5 h-3.5" style={{ color: '#9b8dd4' }} />
</div>
<span className="text-sm font-semibold text-gray-100">Mind Melder</span>
</div>
<button
onClick={() => setIsDrawerOpen(true)}
className="p-2 text-gray-400 hover:text-gray-200 rounded-lg hover:bg-gray-800"
className="p-2 text-gray-400 hover:text-gray-200 rounded-lg hover:bg-white/[0.06]"
style={isElectron ? { WebkitAppRegion: 'no-drag' } as React.CSSProperties : undefined}
>
<Menu className="w-6 h-6" />
<Menu className="w-5 h-5" />
</button>
</div>
</div>

{/* Mobile Drawer Overlay */}
{isDrawerOpen && (
<div
className="md:hidden fixed inset-0 bg-black/50 z-50"
className="md:hidden fixed inset-0 bg-black/60 backdrop-blur-sm z-50"
onClick={closeDrawer}
/>
)}

{/* Mobile Drawer */}
<div
className={`md:hidden fixed top-0 right-0 h-full w-72 bg-gray-900 border-l border-gray-800 z-50 transform transition-transform duration-300 ease-in-out ${
className={`md:hidden fixed top-0 right-0 h-full w-72 z-50 transform transition-transform duration-300 ease-in-out ${
isDrawerOpen ? 'translate-x-0' : 'translate-x-full'
}`}
style={{ backgroundColor: '#0c1117', borderLeft: '1px solid rgba(255,255,255,0.05)' }}
>
<div className="flex items-center justify-between p-4 border-b border-gray-800">
<span className="font-semibold text-gray-200">Menu</span>
<div className="flex items-center justify-between p-4" style={{ borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
<span className="text-sm font-semibold text-gray-300">Navigation</span>
<button
onClick={closeDrawer}
className="p-2 text-gray-400 hover:text-gray-200 rounded-lg hover:bg-gray-800"
className="p-1.5 text-gray-400 hover:text-gray-200 rounded-lg hover:bg-white/[0.06]"
>
<X className="w-5 h-5" />
<X className="w-4 h-4" />
</button>
</div>

<nav className="py-4">
{navItems.map((item) => {
const Icon = item.icon;
return (
<Link
key={item.path}
to={item.path}
onClick={closeDrawer}
className={`
flex items-center gap-3 px-6 py-3 transition-all
${
isActive(item.path)
? 'bg-gray-800 border-l-4 border-accent text-gray-100'
: 'text-gray-400 hover:text-gray-200 hover:bg-gray-800/50'
}
`}
<nav className="py-3">
<p className="px-5 mb-1 mt-2 text-[10px] uppercase tracking-widest text-gray-600 font-semibold">Workspace</p>
<div className="space-y-0.5">
{primaryNavItems.map((item) => (
<NavLink key={item.path} item={item} onClick={closeDrawer} />
))}
</div>
<p className="px-5 mb-1 mt-5 text-[10px] uppercase tracking-widest text-gray-600 font-semibold">Tools</p>
<div className="space-y-0.5">
{toolNavItems.map((item) => (
<NavLink key={item.path} item={item} onClick={closeDrawer} />
))}
</div>
<div className="mt-4 pt-3 space-y-0.5" style={{ borderTop: '1px solid rgba(255,255,255,0.05)' }}>
<NavLink item={{ path: '/settings', label: 'Settings', icon: Cog }} onClick={closeDrawer} />
{isElectron && (
<button
onClick={() => {
closeDrawer();
window.electronAPI?.openRecordingWindow();
}}
className="flex items-center gap-3 mx-2 px-3 py-2 rounded-lg text-sm font-medium transition-all duration-150 text-gray-500 hover:text-gray-200 hover:bg-white/[0.05] w-[calc(100%-16px)]"
>
<Icon className="w-5 h-5" />
<span className="font-medium">{item.label}</span>
{item.path === '/inbox' && inboxCount > 0 && (
<span className="badge-accent">
{inboxCount}
</span>
)}
</Link>
);
})}
{isElectron && (
<button
onClick={() => {
closeDrawer();
window.electronAPI?.openRecordingWindow();
}}
className="flex items-center gap-3 px-6 py-3 transition-all text-gray-400 hover:text-gray-200 hover:bg-gray-800/50 w-full"
>
<Mic className="w-5 h-5" />
<span className="font-medium">Record</span>
</button>
)}
<Mic className="w-4 h-4 flex-shrink-0" />
<span>Record</span>
</button>
)}
</div>
</nav>
</div>

{/* Draggable title bar overlay for Electron - always on top, even above modals */}
{/* Draggable title bar overlay for Electron */}
{isElectron && (
<div
className="hidden md:block fixed top-0 left-0 right-0 h-8 z-[60]"
Expand All @@ -194,19 +217,18 @@ export default function Layout({ children }: LayoutProps) {
)}

{/* Main content */}
<main className="flex-1 flex flex-col overflow-hidden pt-14 md:pt-0">
{/* Draggable title bar region for Electron - main content area */}
<main className="flex-1 flex flex-col overflow-hidden pt-14 md:pt-0" style={{ backgroundColor: '#070b0f' }}>
{isElectron && (
<div
className="hidden md:block h-8 w-full flex-shrink-0 bg-gray-950 z-10"
style={{ WebkitAppRegion: 'drag' } as React.CSSProperties}
className="hidden md:block h-8 w-full flex-shrink-0 z-10"
style={{ WebkitAppRegion: 'drag', backgroundColor: '#070b0f' } as React.CSSProperties}
/>
)}
{location.pathname.startsWith('/chat') ? (
<div className="flex-1 min-h-0">{children}</div>
) : (
<div className="flex-1 min-h-0 overflow-auto">
<div className="max-w-5xl mx-auto p-4 md:p-8" style={{ paddingBottom: 'calc(1rem + env(safe-area-inset-bottom))' }}>{children}</div>
<div className="max-w-5xl mx-auto p-4 md:p-8" style={{ paddingBottom: 'calc(2rem + env(safe-area-inset-bottom))' }}>{children}</div>
</div>
)}
</main>
Expand Down
Loading
Loading