refactor: migrate to monorepo structure with Docker, PocketBase, and e2e tests
- Reorganize into apps/, packages/, docs/, e2e/, pocketbase/ directories - Add Dockerfiles for web, worker, and PocketBase services - Add docker-compose.yml for local orchestration - Add turbo.json for monorepo task management - Add Playwright e2e test infrastructure - Add PocketBase backend with migrations - Remove Vite/Next.js/ESLint/PostCSS config files - Update package.json with workspace dependencies - Add .env.example and .dockerignore
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useKeyboardShortcutsStore } from '@/lib/stores/use-keyboard-shortcuts-store';
|
||||
|
||||
export function useKeyboardShortcuts() {
|
||||
const router = useRouter();
|
||||
const { shortcuts, enabled } = useKeyboardShortcutsStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
|
||||
let pendingKey = '';
|
||||
let pendingTimeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
// Ignore if user is typing in an input/textarea
|
||||
const target = e.target as HTMLElement;
|
||||
if (
|
||||
target.tagName === 'INPUT' ||
|
||||
target.tagName === 'TEXTAREA' ||
|
||||
target.isContentEditable
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore if modifier keys are pressed (except for our shortcuts)
|
||||
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
||||
|
||||
const key = e.key.toLowerCase();
|
||||
|
||||
// Handle two-key combos (G then letter)
|
||||
if (pendingKey) {
|
||||
clearTimeout(pendingTimeout);
|
||||
pendingKey = '';
|
||||
|
||||
if (key === 'd') { router.push('/dashboard'); e.preventDefault(); return; }
|
||||
if (key === 't') { router.push('/tasks'); e.preventDefault(); return; }
|
||||
if (key === 'h') { router.push('/habits'); e.preventDefault(); return; }
|
||||
if (key === 'p') { router.push('/projects'); e.preventDefault(); return; }
|
||||
if (key === 'n') { router.push('/notes'); e.preventDefault(); return; }
|
||||
if (key === 'r') { router.push('/reports'); e.preventDefault(); return; }
|
||||
if (key === 'c') { router.push('/calendar'); e.preventDefault(); return; }
|
||||
if (key === 'a') { router.push('/analytics'); e.preventDefault(); return; }
|
||||
return;
|
||||
}
|
||||
|
||||
// Single key shortcuts
|
||||
switch (key) {
|
||||
case 'g':
|
||||
pendingKey = 'g';
|
||||
pendingTimeout = setTimeout(() => { pendingKey = ''; }, 1000);
|
||||
e.preventDefault();
|
||||
break;
|
||||
case '/':
|
||||
// Focus search
|
||||
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'k', metaKey: true }));
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'n':
|
||||
// New (context-dependent)
|
||||
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'k', metaKey: true }));
|
||||
e.preventDefault();
|
||||
break;
|
||||
case '?':
|
||||
// Show shortcuts help
|
||||
console.log('Show shortcuts help');
|
||||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
return () => document.removeEventListener('keydown', handleKeyDown);
|
||||
}, [router, enabled, shortcuts]);
|
||||
}
|
||||
Reference in New Issue
Block a user