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:
2026-07-16 06:19:58 -04:00
parent ec14645a4b
commit 8f55626e03
286 changed files with 31992 additions and 9245 deletions
+6
View File
@@ -0,0 +1,6 @@
export { useThemeStore } from './use-theme-store';
export { useSidebarStore } from './use-sidebar-store';
export { useDashboardStore } from './use-dashboard-store';
export { useTimerStore } from './use-timer-store';
export { useFilterStore } from './use-filter-store';
export { useKeyboardShortcutsStore } from './use-keyboard-shortcuts-store';
@@ -0,0 +1,62 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface WidgetConfig {
id: string;
type: string;
x: number;
y: number;
w: number;
h: number;
visible: boolean;
}
interface DashboardState {
widgets: WidgetConfig[];
// Actions
setWidgets: (widgets: WidgetConfig[]) => void;
updateWidget: (id: string, updates: Partial<WidgetConfig>) => void;
addWidget: (widget: WidgetConfig) => void;
removeWidget: (id: string) => void;
resetLayout: () => void;
}
const defaultWidgets: WidgetConfig[] = [
{ id: 'today-tasks', type: 'TodayTasks', x: 0, y: 0, w: 6, h: 4, visible: true },
{ id: 'habit-checklist', type: 'HabitChecklist', x: 6, y: 0, w: 3, h: 4, visible: true },
{ id: 'weekly-stats', type: 'WeeklyStats', x: 9, y: 0, w: 3, h: 4, visible: true },
{ id: 'project-progress', type: 'ProjectProgress', x: 0, y: 4, w: 4, h: 3, visible: true },
{ id: 'habit-streaks', type: 'HabitStreaks', x: 4, y: 4, w: 4, h: 3, visible: true },
{ id: 'calendar-mini', type: 'CalendarMini', x: 8, y: 4, w: 4, h: 3, visible: true },
{ id: 'quick-add', type: 'QuickAdd', x: 0, y: 7, w: 3, h: 3, visible: true },
{ id: 'recent-activity', type: 'RecentActivity', x: 3, y: 7, w: 9, h: 3, visible: true },
];
export const useDashboardStore = create<DashboardState>()(
persist(
(set) => ({
widgets: defaultWidgets,
setWidgets: (widgets) => set({ widgets }),
updateWidget: (id, updates) =>
set((state) => ({
widgets: state.widgets.map((w) =>
w.id === id ? { ...w, ...updates } : w
),
})),
addWidget: (widget) =>
set((state) => ({
widgets: [...state.widgets, widget],
})),
removeWidget: (id) =>
set((state) => ({
widgets: state.widgets.filter((w) => w.id !== id),
})),
resetLayout: () => set({ widgets: defaultWidgets }),
}),
{
name: 'project-e-dashboard',
}
)
);
+57
View File
@@ -0,0 +1,57 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface FilterState {
domain: string | null;
project: string | null;
tags: string[];
status: string | null;
priority: string | null;
// Actions
setDomain: (domain: string | null) => void;
setProject: (project: string | null) => void;
setTags: (tags: string[]) => void;
addTag: (tag: string) => void;
removeTag: (tag: string) => void;
setStatus: (status: string | null) => void;
setPriority: (priority: string | null) => void;
clearAll: () => void;
}
export const useFilterStore = create<FilterState>()(
persist(
(set) => ({
domain: null,
project: null,
tags: [],
status: null,
priority: null,
setDomain: (domain) => set({ domain }),
setProject: (project) => set({ project }),
setTags: (tags) => set({ tags }),
addTag: (tag) =>
set((state) => ({
tags: state.tags.includes(tag) ? state.tags : [...state.tags, tag],
})),
removeTag: (tag) =>
set((state) => ({
tags: state.tags.filter((t) => t !== tag),
})),
setStatus: (status) => set({ status }),
setPriority: (priority) => set({ priority }),
clearAll: () =>
set({
domain: null,
project: null,
tags: [],
status: null,
priority: null,
}),
}),
{
name: 'project-e-filters',
}
)
);
@@ -0,0 +1,61 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface Shortcut {
key: string;
description: string;
action: string;
enabled: boolean;
}
interface KeyboardShortcutsState {
enabled: boolean;
shortcuts: Shortcut[];
// Actions
setEnabled: (enabled: boolean) => void;
updateShortcut: (key: string, updates: Partial<Shortcut>) => void;
resetShortcuts: () => void;
}
const defaultShortcuts: Shortcut[] = [
{ key: 'g+d', description: 'Go to Dashboard', action: 'navigate_dashboard', enabled: true },
{ key: 'g+t', description: 'Go to Tasks', action: 'navigate_tasks', enabled: true },
{ key: 'g+h', description: 'Go to Habits', action: 'navigate_habits', enabled: true },
{ key: 'g+p', description: 'Go to Projects', action: 'navigate_projects', enabled: true },
{ key: 'g+n', description: 'Go to Notes', action: 'navigate_notes', enabled: true },
{ key: 'g+r', description: 'Go to Reports', action: 'navigate_reports', enabled: true },
{ key: 'g+c', description: 'Go to Calendar', action: 'navigate_calendar', enabled: true },
{ key: 'g+a', description: 'Go to Analytics', action: 'navigate_analytics', enabled: true },
{ key: '/', description: 'Focus search', action: 'focus_search', enabled: true },
{ key: 'n', description: 'Quick add', action: 'quick_add', enabled: true },
{ key: '?', description: 'Show shortcuts help', action: 'show_help', enabled: true },
{ key: 'j', description: 'Move down', action: 'move_down', enabled: true },
{ key: 'k', description: 'Move up', action: 'move_up', enabled: true },
{ key: 'enter', description: 'Open selected', action: 'open', enabled: true },
{ key: 'e', description: 'Edit selected', action: 'edit', enabled: true },
{ key: 'space', description: 'Toggle complete', action: 'toggle_complete', enabled: true },
{ key: 'c', description: 'Complete selected', action: 'complete', enabled: true },
{ key: 'x', description: 'Cancel', action: 'cancel', enabled: true },
];
export const useKeyboardShortcutsStore = create<KeyboardShortcutsState>()(
persist(
(set) => ({
enabled: true,
shortcuts: defaultShortcuts,
setEnabled: (enabled) => set({ enabled }),
updateShortcut: (key, updates) =>
set((state) => ({
shortcuts: state.shortcuts.map((s) =>
s.key === key ? { ...s, ...updates } : s
),
})),
resetShortcuts: () => set({ shortcuts: defaultShortcuts }),
}),
{
name: 'project-e-keyboard-shortcuts',
}
)
);
+28
View File
@@ -0,0 +1,28 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface SidebarState {
collapsed: boolean;
mobileOpen: boolean;
// Actions
toggle: () => void;
setCollapsed: (collapsed: boolean) => void;
setMobileOpen: (open: boolean) => void;
}
export const useSidebarStore = create<SidebarState>()(
persist(
(set) => ({
collapsed: false,
mobileOpen: false,
toggle: () => set((state) => ({ collapsed: !state.collapsed })),
setCollapsed: (collapsed) => set({ collapsed }),
setMobileOpen: (mobileOpen) => set({ mobileOpen }),
}),
{
name: 'project-e-sidebar',
}
)
);
+41
View File
@@ -0,0 +1,41 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
type ThemeMode = 'light' | 'dark' | 'system';
type Density = 'compact' | 'comfortable' | 'spacious';
interface ThemeState {
mode: ThemeMode;
accent: string;
font: string;
density: Density;
reducedMotion: boolean;
// Actions
setMode: (mode: ThemeMode) => void;
setAccent: (color: string) => void;
setFont: (font: string) => void;
setDensity: (density: Density) => void;
setReducedMotion: (enabled: boolean) => void;
}
export const useThemeStore = create<ThemeState>()(
persist(
(set) => ({
mode: 'system',
accent: '#356bff',
font: 'geist',
density: 'comfortable',
reducedMotion: false,
setMode: (mode) => set({ mode }),
setAccent: (accent) => set({ accent }),
setFont: (font) => set({ font }),
setDensity: (density) => set({ density }),
setReducedMotion: (reducedMotion) => set({ reducedMotion }),
}),
{
name: 'project-e-theme',
}
)
);
+71
View File
@@ -0,0 +1,71 @@
import { create } from 'zustand';
type TimerMode = 'focus' | 'break' | 'idle';
interface TimerState {
mode: TimerMode;
isRunning: boolean;
secondsRemaining: number;
focusMinutes: number;
breakMinutes: number;
currentTaskId: string | null;
// Actions
startFocus: (taskId?: string) => void;
startBreak: () => void;
pause: () => void;
resume: () => void;
reset: () => void;
tick: () => void;
setFocusMinutes: (minutes: number) => void;
setBreakMinutes: (minutes: number) => void;
}
export const useTimerStore = create<TimerState>()((set, get) => ({
mode: 'idle',
isRunning: false,
secondsRemaining: 0,
focusMinutes: 25,
breakMinutes: 5,
currentTaskId: null,
startFocus: (taskId) =>
set({
mode: 'focus',
isRunning: true,
secondsRemaining: get().focusMinutes * 60,
currentTaskId: taskId ?? null,
}),
startBreak: () =>
set({
mode: 'break',
isRunning: true,
secondsRemaining: get().breakMinutes * 60,
}),
pause: () => set({ isRunning: false }),
resume: () => set({ isRunning: true }),
reset: () =>
set({
mode: 'idle',
isRunning: false,
secondsRemaining: 0,
currentTaskId: null,
}),
tick: () => {
const state = get();
if (!state.isRunning || state.secondsRemaining <= 0) {
if (state.secondsRemaining <= 0) {
set({ isRunning: false, mode: 'idle' });
}
return;
}
set({ secondsRemaining: state.secondsRemaining - 1 });
},
setFocusMinutes: (focusMinutes) => set({ focusMinutes }),
setBreakMinutes: (breakMinutes) => set({ breakMinutes }),
}));