summaryrefslogtreecommitdiff
path: root/src/App.tsx
diff options
context:
space:
mode:
authorSolstice <solstice@local>2026-06-09 00:22:18 -0700
committerSolstice <solstice@local>2026-06-09 00:22:18 -0700
commit6cb006cc136f1fc5c83537cc30c64d223d1755e4 (patch)
treeb12dfb4a8345c980dc8747657ce381016cdf3b34 /src/App.tsx
parent25e1dcf205cd14feafdd9b4cf6b7a66f253ba6d2 (diff)
feat: frontend view, state management, and user interface
Diffstat (limited to 'src/App.tsx')
-rw-r--r--src/App.tsx113
1 files changed, 108 insertions, 5 deletions
diff --git a/src/App.tsx b/src/App.tsx
index a6e3249..966e8e1 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,11 +1,114 @@
+import { useEffect, useState, useCallback } from 'react';
+import { TimerView } from './components/TimerView';
+import { TaskList } from './components/TaskList';
+import { SettingsPanel } from './components/SettingsPanel';
+import { NotificationOverlay } from './components/NotificationOverlay';
+import { useTimerEvents } from './hooks/useTimerEvents';
+import { useTaskStore } from './store/taskStore';
+import { useSettingsStore } from './store/settingsStore';
+
+function GearIcon() {
+ return (
+ <svg
+ width="18"
+ height="18"
+ viewBox="0 0 24 24"
+ fill="none"
+ stroke="currentColor"
+ strokeWidth="1.5"
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ >
+ <circle cx="12" cy="12" r="3" />
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
+ </svg>
+ );
+}
+
function App() {
+ const [settingsOpen, setSettingsOpen] = useState(false);
+ const [notifVisible, setNotifVisible] = useState(false);
+ const [notifTaskId, setNotifTaskId] = useState<string | null>(null);
+
+ const fetchTasks = useTaskStore((s) => s.fetchTasks);
+ const fetchSettings = useSettingsStore((s) => s.fetchSettings);
+
+ // Bootstrap data on mount
+ useEffect(() => {
+ fetchTasks();
+ fetchSettings();
+ }, [fetchTasks, fetchSettings]);
+
+ const handleCompleted = useCallback((taskId: string | null) => {
+ setNotifTaskId(taskId);
+ setNotifVisible(true);
+ }, []);
+
+ useTimerEvents(handleCompleted);
+
return (
- <main className="flex items-center justify-center min-h-screen">
- <div className="text-center">
- <h1 className="font-display text-fg-1 text-4xl mb-2">Solstice</h1>
- <p className="text-fg-3">Pomodoro timer with ambient soundscapes.</p>
+ <div
+ style={{
+ display: 'flex',
+ width: '100vw',
+ height: '100vh',
+ background: 'var(--ink-0)',
+ overflow: 'hidden',
+ position: 'relative',
+ }}
+ >
+ {/* Sidebar */}
+ <TaskList />
+
+ {/* Main area */}
+ <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
+ {/* Top bar */}
+ <div
+ style={{
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'flex-end',
+ padding: '12px 16px',
+ borderBottom: '1px solid var(--line-1)',
+ }}
+ >
+ <button
+ onClick={() => setSettingsOpen(true)}
+ title="Settings"
+ style={{
+ display: 'flex',
+ alignItems: 'center',
+ padding: '6px',
+ border: 'none',
+ background: 'transparent',
+ color: 'var(--fg-3)',
+ cursor: 'pointer',
+ borderRadius: 'var(--r-2)',
+ transition: 'color 0.15s ease',
+ }}
+ onMouseEnter={(e) => {
+ (e.currentTarget as HTMLButtonElement).style.color = 'var(--fg-1)';
+ }}
+ onMouseLeave={(e) => {
+ (e.currentTarget as HTMLButtonElement).style.color = 'var(--fg-3)';
+ }}
+ >
+ <GearIcon />
+ </button>
+ </div>
+
+ {/* Timer */}
+ <TimerView />
</div>
- </main>
+
+ {/* Overlays */}
+ <SettingsPanel open={settingsOpen} onClose={() => setSettingsOpen(false)} />
+ <NotificationOverlay
+ visible={notifVisible}
+ taskId={notifTaskId}
+ onDismiss={() => setNotifVisible(false)}
+ />
+ </div>
);
}