summaryrefslogtreecommitdiff
path: root/src/components/SettingsPanel.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/SettingsPanel.tsx')
-rw-r--r--src/components/SettingsPanel.tsx240
1 files changed, 240 insertions, 0 deletions
diff --git a/src/components/SettingsPanel.tsx b/src/components/SettingsPanel.tsx
new file mode 100644
index 0000000..0d3cbf9
--- /dev/null
+++ b/src/components/SettingsPanel.tsx
@@ -0,0 +1,240 @@
+import { useState, useEffect } from 'react';
+import { useSettingsStore, Settings } from '../store/settingsStore';
+
+interface SettingsPanelProps {
+ open: boolean;
+ onClose: () => void;
+}
+
+export function SettingsPanel({ open, onClose }: SettingsPanelProps) {
+ const { settings, fetchSettings, updateSettings } = useSettingsStore();
+
+ const [workMins, setWorkMins] = useState(25);
+ const [shortBreakMins, setShortBreakMins] = useState(5);
+ const [longBreakMins, setLongBreakMins] = useState(15);
+ const [sessionsBeforeLong, setSessionsBeforeLong] = useState(4);
+
+ useEffect(() => {
+ if (open && !settings) {
+ fetchSettings();
+ }
+ }, [open, settings, fetchSettings]);
+
+ useEffect(() => {
+ if (settings) {
+ setWorkMins(Math.round(settings.work_duration_secs / 60));
+ setShortBreakMins(Math.round(settings.short_break_secs / 60));
+ setLongBreakMins(Math.round(settings.long_break_secs / 60));
+ setSessionsBeforeLong(settings.sessions_before_long_break);
+ }
+ }, [settings]);
+
+ if (!open) return null;
+
+ const handleSave = async () => {
+ const s: Settings = {
+ work_duration_secs: workMins * 60,
+ short_break_secs: shortBreakMins * 60,
+ long_break_secs: longBreakMins * 60,
+ sessions_before_long_break: sessionsBeforeLong,
+ };
+ await updateSettings(s);
+ onClose();
+ };
+
+ return (
+ <div
+ onClick={onClose}
+ style={{
+ position: 'fixed',
+ inset: 0,
+ background: 'rgba(11, 14, 19, 0.70)',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ zIndex: 100,
+ }}
+ >
+ {/* Modal card */}
+ <div
+ onClick={(e) => e.stopPropagation()}
+ style={{
+ background: 'var(--ink-1)',
+ border: '1px solid var(--line-2)',
+ borderRadius: 'var(--r-3)',
+ boxShadow: 'var(--shadow-3)',
+ padding: '32px',
+ width: '360px',
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '24px',
+ }}
+ >
+ <span className="eyebrow" style={{ letterSpacing: '0.18em' }}>
+ Settings
+ </span>
+
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
+ <NumberField
+ label="Work duration"
+ unit="min"
+ value={workMins}
+ min={1}
+ max={120}
+ onChange={setWorkMins}
+ />
+ <NumberField
+ label="Short break"
+ unit="min"
+ value={shortBreakMins}
+ min={1}
+ max={60}
+ onChange={setShortBreakMins}
+ />
+ <NumberField
+ label="Long break"
+ unit="min"
+ value={longBreakMins}
+ min={1}
+ max={60}
+ onChange={setLongBreakMins}
+ />
+ <NumberField
+ label="Sessions before long break"
+ unit="sessions"
+ value={sessionsBeforeLong}
+ min={1}
+ max={10}
+ onChange={setSessionsBeforeLong}
+ />
+ </div>
+
+ <div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
+ <button
+ onClick={onClose}
+ style={{
+ fontFamily: 'var(--font-sans)',
+ fontSize: '14px',
+ fontWeight: 500,
+ padding: '8px 16px',
+ borderRadius: 'var(--r-1)',
+ border: '1px solid var(--line-2)',
+ background: 'transparent',
+ color: 'var(--brass)',
+ cursor: 'pointer',
+ transition: 'border-color 0.15s ease',
+ }}
+ onMouseEnter={(e) => {
+ (e.currentTarget as HTMLButtonElement).style.borderColor = 'var(--brass)';
+ }}
+ onMouseLeave={(e) => {
+ (e.currentTarget as HTMLButtonElement).style.borderColor = 'var(--line-2)';
+ }}
+ >
+ Cancel
+ </button>
+ <button
+ onClick={handleSave}
+ style={{
+ fontFamily: 'var(--font-sans)',
+ fontSize: '14px',
+ fontWeight: 600,
+ padding: '8px 20px',
+ borderRadius: 'var(--r-1)',
+ border: 'none',
+ background: 'var(--brass)',
+ color: 'var(--brass-ink)',
+ cursor: 'pointer',
+ transition: 'background 0.15s ease',
+ }}
+ onMouseEnter={(e) => {
+ (e.currentTarget as HTMLButtonElement).style.background = 'var(--brass-bright)';
+ }}
+ onMouseLeave={(e) => {
+ (e.currentTarget as HTMLButtonElement).style.background = 'var(--brass)';
+ }}
+ onMouseDown={(e) => {
+ (e.currentTarget as HTMLButtonElement).style.background = 'var(--brass-deep)';
+ }}
+ onMouseUp={(e) => {
+ (e.currentTarget as HTMLButtonElement).style.background = 'var(--brass-bright)';
+ }}
+ >
+ Save
+ </button>
+ </div>
+ </div>
+ </div>
+ );
+}
+
+interface NumberFieldProps {
+ label: string;
+ unit: string;
+ value: number;
+ min: number;
+ max: number;
+ onChange: (v: number) => void;
+}
+
+function NumberField({ label, unit, value, min, max, onChange }: NumberFieldProps) {
+ return (
+ <div
+ style={{
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ gap: '16px',
+ }}
+ >
+ <label
+ style={{
+ fontFamily: 'var(--font-sans)',
+ fontSize: '14px',
+ color: 'var(--fg-2)',
+ flex: 1,
+ }}
+ >
+ {label}
+ </label>
+ <div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
+ <input
+ type="number"
+ min={min}
+ max={max}
+ value={value}
+ onChange={(e) => onChange(Math.min(max, Math.max(min, parseInt(e.target.value) || min)))}
+ style={{
+ fontFamily: 'var(--font-mono)',
+ fontSize: '14px',
+ fontVariantNumeric: 'tabular-nums',
+ padding: '5px 8px',
+ borderRadius: 'var(--r-2)',
+ border: '1px solid var(--line-2)',
+ background: 'var(--ink-3)',
+ color: 'var(--fg-1)',
+ outline: 'none',
+ width: '56px',
+ textAlign: 'center',
+ }}
+ onFocus={(e) => {
+ (e.currentTarget as HTMLInputElement).style.boxShadow = 'var(--shadow-brass)';
+ }}
+ onBlur={(e) => {
+ (e.currentTarget as HTMLInputElement).style.boxShadow = 'none';
+ }}
+ />
+ <span
+ style={{
+ fontFamily: 'var(--font-sans)',
+ fontSize: '12px',
+ color: 'var(--fg-4)',
+ minWidth: '44px',
+ }}
+ >
+ {unit}
+ </span>
+ </div>
+ </div>
+ );
+}