From 0e854b6e9b5a374ae568cbc27c0914cc83ae2d7b Mon Sep 17 00:00:00 2001 From: mARTin Date: Fri, 13 Feb 2026 16:53:44 +0100 Subject: [PATCH] feat: implement settings sidebar and user preferences - Refactored SettingsModal to a slide-in SettingsSidebar - Added comprehensive preference toggles (Someday, All-Day, Schedule) - Integrated Google Fonts selection (Headline/Body/Weight) - Synchronized all preferences with DB and WeeklyView state - Fixed sidebar positioning and overlay CSS --- prisma/schema.prisma | 6 +- src/app/api/user/profile/route.ts | 17 +- src/app/globals.css | 105 +++++++++++- src/components/UserMenu.tsx | 13 +- src/components/WeeklyView.tsx | 274 +++++++++++++++--------------- 5 files changed, 258 insertions(+), 157 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a4ca424..0beee4f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -32,13 +32,17 @@ model User { showNextTask Boolean @default(false) calendarEditMode Boolean @default(false) focusTimerDuration Int @default(25) + focusBreakDuration Int @default(5) showTimeGrid Boolean @default(true) + showSomeday Boolean @default(true) + showAllDayEvents Boolean @default(true) + showSchedule Boolean @default(true) cellDuration Int @default(30) viewStyle String @default("grid") fontSize String @default("M") // "S", "M", "L" headlineFont String @default("Inter") bodyFont String @default("Inter") - fontWeight String @default("normal") // "light", "normal", "bold" + fontWeight String @default("400") // "300", "400", "500", "700" accounts Account[] sessions Session[] diff --git a/src/app/api/user/profile/route.ts b/src/app/api/user/profile/route.ts index a953dc3..2801e50 100644 --- a/src/app/api/user/profile/route.ts +++ b/src/app/api/user/profile/route.ts @@ -27,7 +27,11 @@ export async function GET(request: NextRequest) { showNextTask: true, calendarEditMode: true, focusTimerDuration: true, + focusBreakDuration: true, showTimeGrid: true, + showSomeday: true, + showAllDayEvents: true, + showSchedule: true, cellDuration: true, viewStyle: true, fontSize: true, @@ -58,8 +62,9 @@ export async function PATCH(request: NextRequest) { const { name, timezone, password, autoRolling, protectEventTimes, language, dateFormat, timeFormat, startHour, endHour, - showNextTask, calendarEditMode, focusTimerDuration, - showTimeGrid, cellDuration, viewStyle, fontSize, + showNextTask, calendarEditMode, focusTimerDuration, focusBreakDuration, + showTimeGrid, showSomeday, showAllDayEvents, showSchedule, + cellDuration, viewStyle, fontSize, headlineFont, bodyFont, fontWeight } = body; @@ -76,7 +81,11 @@ export async function PATCH(request: NextRequest) { ...(showNextTask !== undefined && { showNextTask }), ...(calendarEditMode !== undefined && { calendarEditMode }), ...(focusTimerDuration !== undefined && !isNaN(focusTimerDuration) && { focusTimerDuration }), + ...(focusBreakDuration !== undefined && !isNaN(focusBreakDuration) && { focusBreakDuration }), ...(showTimeGrid !== undefined && { showTimeGrid }), + ...(showSomeday !== undefined && { showSomeday }), + ...(showAllDayEvents !== undefined && { showAllDayEvents }), + ...(showSchedule !== undefined && { showSchedule }), ...(cellDuration !== undefined && !isNaN(cellDuration) && { cellDuration }), ...(viewStyle !== undefined && { viewStyle }), ...(fontSize !== undefined && { fontSize }), @@ -106,7 +115,11 @@ export async function PATCH(request: NextRequest) { showNextTask: true, calendarEditMode: true, focusTimerDuration: true, + focusBreakDuration: true, showTimeGrid: true, + showSomeday: true, + showAllDayEvents: true, + showSchedule: true, cellDuration: true, viewStyle: true, fontSize: true, diff --git a/src/app/globals.css b/src/app/globals.css index bb0d1ad..0494a81 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1400,8 +1400,8 @@ h3 { .time-slot-label { display: flex; align-items: flex-end; - justify-content: flex-end; - padding: 0 0.25rem; + justify-content: flex-start; + padding: 0 0.5rem; font-size: 0.65rem; color: var(--weekly-text-light); box-sizing: border-box; @@ -2293,3 +2293,104 @@ h3 { /* Ensure they mix/cross-fade or slide as expected */ /* Default is usually fine, but duration control is key */ } + +/* ============================================ + SETTINGS SIDEBAR STYLES + ============================================ */ +.weekly-settings-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.3); + z-index: 2000; + backdrop-filter: blur(2px); + opacity: 0; + visibility: hidden; + transition: opacity 0.3s ease; + display: block; /* Overriding possible flex from parent if any */ +} + +.weekly-settings-overlay.show { + opacity: 1; + visibility: visible; +} + +.weekly-settings-sidebar { + position: fixed; + top: 0; + right: 0; + width: 500px; + max-width: 90vw; + height: 100vh; + background: white; + box-shadow: -10px 0 30px rgba(0, 0, 0, 0.1); + display: flex; + flex-direction: column; + transform: translateX(100%); + transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); + z-index: 2001; + overflow: hidden; + border-left: 1px solid #eee; +} + +.weekly-settings-sidebar.open { + transform: translateX(0); +} + +.dark-mode .weekly-settings-sidebar { + background: #111; + border-left: 1px solid #333; + color: white; +} + +.weekly-settings-sidebar .weekly-settings-header { + padding: 24px; + border-bottom: 1px solid #eee; + display: flex; + align-items: center; + justify-content: space-between; +} + +.dark-mode .weekly-settings-sidebar .weekly-settings-header { + border-bottom-color: #333; +} + +.weekly-settings-sidebar .weekly-settings-content { + flex: 1; + overflow-y: auto; + padding: 24px; + display: flex; + flex-direction: column; + gap: 24px; +} + +.weekly-settings-sidebar .weekly-settings-footer { + padding: 20px 24px; + border-top: 1px solid #eee; + background: #f9f9f9; + display: flex; + justify-content: flex-end; + gap: 12px; +} + +.dark-mode .weekly-settings-sidebar .weekly-settings-footer { + background: #1a1a1a; + border-top-color: #333; +} + +.toggle-checkbox { + width: 18px; + height: 18px; + cursor: pointer; +} + +.settings-section-title { + font-size: 0.75rem; + font-weight: 700; + color: #999; + text-transform: uppercase; + letter-spacing: 0.1em; + margin-bottom: 12px; +} diff --git a/src/components/UserMenu.tsx b/src/components/UserMenu.tsx index 3e4c94a..4c76597 100644 --- a/src/components/UserMenu.tsx +++ b/src/components/UserMenu.tsx @@ -3,12 +3,11 @@ import { signOut } from 'next-auth/react'; interface UserMenuProps { userEmail?: string | null; - onOpenRecurring: () => void; onOpenSettings: () => void; trigger?: React.ReactNode; } -export default function UserMenu({ userEmail, onOpenRecurring, onOpenSettings, trigger }: UserMenuProps) { +export default function UserMenu({ userEmail, onOpenSettings, trigger }: UserMenuProps) { const [isOpen, setIsOpen] = useState(false); const menuRef = useRef(null); @@ -57,16 +56,6 @@ export default function UserMenu({ userEmail, onOpenRecurring, onOpenSettings, t Settings -
diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx index d699abf..26aa570 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -353,6 +353,8 @@ export default function WeeklyView() { const [calendarEditMode, setCalendarEditMode] = useState(false); const [selectedTaskForRecurrence, setSelectedTaskForRecurrence] = useState(null); const [showFocusMode, setShowFocusMode] = useState(false); + const [showSchedule, setShowSchedule] = useState(true); + const [focusBreakDuration, setFocusBreakDuration] = useState(5); // New UI State const [isSearchOpen, setIsSearchOpen] = useState(false); @@ -631,6 +633,12 @@ export default function WeeklyView() { endHour: number; fontSize: 'S' | 'M' | 'L'; showNextTask: boolean; + showSomeday: boolean; + showAllDayEvents: boolean; + showSchedule: boolean; + headlineFont: string; + bodyFont: string; + fontWeight: string; }) => { setShowTimeGrid(newSettings.showTimeGrid); setCellDuration(newSettings.cellDuration); @@ -642,9 +650,14 @@ export default function WeeklyView() { setEndHour(newSettings.endHour); setFontSize(newSettings.fontSize); setShowNextTask(newSettings.showNextTask); + setShowSomeday(newSettings.showSomeday); + setShowAllDay(newSettings.showAllDayEvents); + setShowSchedule(newSettings.showSchedule); + setHeadlineFont(newSettings.headlineFont); + setBodyFont(newSettings.bodyFont); + setFontWeight(newSettings.fontWeight); + // Custom start/end hours might affect task placement if we filter strictly - // But mainly we just re-render. Fetching tasks again isn't strictly necessary unless filtering changed on backend. - // But let's do it to be safe if backend filtering relies on these. fetchTasks(); }; @@ -663,6 +676,18 @@ export default function WeeklyView() { setShowNextTask(data.user.showNextTask || false); setCalendarEditMode(data.user.calendarEditMode || false); if (data.user.fontSize) setFontSize(data.user.fontSize as 'S' | 'M' | 'L'); + + if (data.user.showSomeday !== undefined) setShowSomeday(data.user.showSomeday); + if (data.user.showAllDayEvents !== undefined) setShowAllDay(data.user.showAllDayEvents); + if (data.user.showSchedule !== undefined) setShowSchedule(data.user.showSchedule); + if (data.user.headlineFont) setHeadlineFont(data.user.headlineFont); + if (data.user.bodyFont) setBodyFont(data.user.bodyFont); + if (data.user.fontWeight) setFontWeight(data.user.fontWeight); + if (data.user.focusTimerDuration) setFocusTimerDuration(data.user.focusTimerDuration); + if (data.user.focusBreakDuration) setFocusBreakDuration(data.user.focusBreakDuration); + if (data.user.showTimeGrid !== undefined) setShowTimeGrid(data.user.showTimeGrid); + if (data.user.cellDuration) setCellDuration(data.user.cellDuration as CellDuration); + if (data.user.viewStyle) setViewStyle(data.user.viewStyle as 'grid' | 'list'); } } } catch (e) { @@ -1690,7 +1715,6 @@ export default function WeeklyView() { {/* User Menu */} setIsRecurringTasksOpen(true)} onOpenSettings={() => setShowPreferences(true)} trigger={ +
@@ -3658,7 +3653,7 @@ function SettingsModal({
-
+
{activeTab === 'general' ? (
{/* Motto */} @@ -3701,6 +3696,19 @@ function SettingsModal({
+
+ setShowSchedule(e.target.checked)} + style={{ width: '16px', height: '16px' }} + /> + +
+
(
setProfile({ ...profile, fontWeight: weight.value })} style={{ width: '16px', height: '16px', cursor: 'pointer' }} @@ -3992,20 +3997,33 @@ function SettingsModal({
-
- - setProfile({ ...profile, focusTimerDuration: parseInt(e.target.value) || 25 })} - style={{ width: '100%', padding: '8px', border: '1px solid #ddd', borderRadius: '4px' }} - /> +
+
+ + setProfile({ ...profile, focusTimerDuration: parseInt(e.target.value) || 25 })} + style={{ width: '100%', padding: '8px', border: '1px solid #ddd', borderRadius: '4px' }} + /> +
+
+ + setProfile({ ...profile, focusBreakDuration: parseInt(e.target.value) || 5 })} + style={{ width: '100%', padding: '8px', border: '1px solid #ddd', borderRadius: '4px' }} + /> +
- {/* Resize Handle */} -
{ - e.stopPropagation(); - setIsResizing(true); - setResizeStart({ - w: modalSize.width, - h: modalSize.height, - x: e.clientX, - y: e.clientY - }); - }} - />
-
+ ); }