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
This commit is contained in:
mARTin 2026-02-13 16:53:44 +01:00
parent f603168494
commit 0e854b6e9b
5 changed files with 258 additions and 157 deletions

View File

@ -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[]

View File

@ -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,

View File

@ -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;
}

View File

@ -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<HTMLDivElement>(null);
@ -57,16 +56,6 @@ export default function UserMenu({ userEmail, onOpenRecurring, onOpenSettings, t
Settings
</button>
<button
onClick={() => { onOpenRecurring(); setIsOpen(false); }}
className="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-gray-400">
<polyline points="23 4 23 10 17 10"></polyline>
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
</svg>
Recurring To-Dos
</button>
<div className="border-t border-gray-100 my-1"></div>

View File

@ -353,6 +353,8 @@ export default function WeeklyView() {
const [calendarEditMode, setCalendarEditMode] = useState(false);
const [selectedTaskForRecurrence, setSelectedTaskForRecurrence] = useState<Task | null>(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 */}
<UserMenu
userEmail={session?.user?.email}
onOpenRecurring={() => setIsRecurringTasksOpen(true)}
onOpenSettings={() => setShowPreferences(true)}
trigger={
<button className="p-1.5 hover:bg-gray-100 rounded-md text-gray-500 hover:text-black transition-colors" title="User Menu">
@ -2584,10 +2608,10 @@ export default function WeeklyView() {
)
}
{/* Settings Modal */}
{/* Settings Sidebar */}
{
showSettings && (
<SettingsModal
<SettingsSidebar
onClose={() => setShowSettings(false)}
onSettingsChanged={handleSettingsChanged}
showTimeGrid={showTimeGrid}
@ -2602,12 +2626,16 @@ export default function WeeklyView() {
setShowSomeday={setShowSomeday}
showAllDay={showAllDay}
setShowAllDay={setShowAllDay}
showSchedule={showSchedule}
setShowSchedule={setShowSchedule}
motto={motto}
setMotto={setMotto}
connections={connections}
onUpdateConnections={setConnections}
focusTimerDuration={focusTimerDuration}
setFocusTimerDuration={setFocusTimerDuration}
focusBreakDuration={focusBreakDuration}
setFocusBreakDuration={setFocusBreakDuration}
fontSize={fontSize}
setFontSize={setFontSize}
showNextTask={showNextTask}
@ -3237,7 +3265,7 @@ function RecurrenceModal({ task, onClose, onSave }: RecurrenceModalProps) {
}
// Settings Modal Component
interface SettingsModalProps {
interface SettingsSidebarProps {
onClose: () => void;
onSettingsChanged?: (newSettings: {
showTimeGrid: boolean;
@ -3250,6 +3278,9 @@ interface SettingsModalProps {
endHour: number;
fontSize: 'S' | 'M' | 'L';
showNextTask: boolean;
showSomeday: boolean;
showAllDayEvents: boolean;
showSchedule: boolean;
headlineFont: string;
bodyFont: string;
fontWeight: string;
@ -3266,12 +3297,16 @@ interface SettingsModalProps {
setShowSomeday: (show: boolean) => void;
showAllDay: boolean;
setShowAllDay: (show: boolean) => void;
showSchedule: boolean;
setShowSchedule: (show: boolean) => void;
motto: string;
setMotto: (motto: string) => void;
connections: any[];
onUpdateConnections: (connections: any[]) => void;
focusTimerDuration: number;
setFocusTimerDuration: (duration: number) => void;
focusBreakDuration: number;
setFocusBreakDuration: (duration: number) => void;
fontSize: 'S' | 'M' | 'L';
setFontSize: (size: 'S' | 'M' | 'L') => void;
showNextTask: boolean;
@ -3284,7 +3319,7 @@ interface SettingsModalProps {
setFontWeight: (weight: string) => void;
}
function SettingsModal({
function SettingsSidebar({
onClose,
onSettingsChanged,
showTimeGrid,
@ -3299,12 +3334,16 @@ function SettingsModal({
setShowSomeday,
showAllDay,
setShowAllDay,
showSchedule,
setShowSchedule,
motto,
setMotto,
connections,
onUpdateConnections,
focusTimerDuration,
setFocusTimerDuration,
focusBreakDuration,
setFocusBreakDuration,
fontSize,
setFontSize,
showNextTask,
@ -3315,14 +3354,15 @@ function SettingsModal({
setBodyFont,
fontWeight,
setFontWeight
}: SettingsModalProps) {
}: SettingsSidebarProps) {
const [activeTab, setActiveTab] = useState<'general' | 'calendar' | 'account'>('general');
// connections state removed (lifted)
const [isLoading, setIsLoading] = useState(true);
const [exportStartDate, setExportStartDate] = useState('');
const [exportEndDate, setExportEndDate] = useState('');
const [passwords, setPasswords] = useState({ new: '', confirm: '' });
const [accountMsg, setAccountMsg] = useState('');
const [isVisible, setIsVisible] = useState(false);
// Account State
const [profile, setProfile] = useState<{
name: string;
email: string;
@ -3335,11 +3375,15 @@ function SettingsModal({
startHour?: number;
endHour?: number;
focusTimerDuration?: number;
focusBreakDuration?: number;
showTimeGrid?: boolean;
cellDuration?: number;
viewStyle?: string;
fontSize?: 'S' | 'M' | 'L';
showNextTask?: boolean;
showSomeday?: boolean;
showAllDayEvents?: boolean;
showSchedule?: boolean;
headlineFont?: string;
bodyFont?: string;
fontWeight?: string;
@ -3355,69 +3399,34 @@ function SettingsModal({
startHour: 8,
endHour: 18,
focusTimerDuration: 25,
focusBreakDuration: 5,
showTimeGrid: true,
cellDuration: 30,
viewStyle: 'list',
fontSize: 'M',
showNextTask: false,
showSomeday: true,
showAllDayEvents: true,
showSchedule: true,
headlineFont: 'Inter',
bodyFont: 'Inter',
fontWeight: '400'
});
// Draggable/Resizable Modal State
const [modalPos, setModalPos] = useState({ x: 0, y: 0 });
const [modalSize, setModalSize] = useState({ width: 800, height: 800 });
const [isDragging, setIsDragging] = useState(false);
const [isResizing, setIsResizing] = useState(false);
const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
const [resizeStart, setResizeStart] = useState({ w: 0, h: 0, x: 0, y: 0 });
const t = translations[profile.language || 'en'] || translations['en'];
const [passwords, setPasswords] = useState({ new: '', confirm: '' });
const [accountMsg, setAccountMsg] = useState('');
useEffect(() => {
// fetchConnections removed (lifted)
fetchProfile();
// Trigger slide-in after mount
const timer = setTimeout(() => setIsVisible(true), 10);
return () => clearTimeout(timer);
}, []);
// fetchConnections function removed
// Add event listeners for dragging and resizing
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (isDragging) {
setModalPos({
x: e.clientX - dragStart.x,
y: e.clientY - dragStart.y
});
}
if (isResizing) {
const newWidth = Math.max(400, resizeStart.w + (e.clientX - resizeStart.x));
const newHeight = Math.max(300, resizeStart.h + (e.clientY - resizeStart.y));
setModalSize({ width: newWidth, height: newHeight });
}
const handleClose = () => {
setIsVisible(false);
setTimeout(onClose, 300);
};
const handleMouseUp = () => {
setIsDragging(false);
setIsResizing(false);
};
if (isDragging || isResizing) {
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
}
return () => {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleMouseUp);
};
}, [isDragging, isResizing, dragStart, resizeStart]);
async function fetchProfile() {
try {
const res = await fetch('/api/user/profile');
@ -3443,6 +3452,8 @@ function SettingsModal({
headlineFont: data.user.headlineFont || 'Inter',
bodyFont: data.user.bodyFont || 'Inter',
fontWeight: data.user.fontWeight || '400',
showSchedule: data.user.showSchedule !== undefined ? data.user.showSchedule : true,
focusBreakDuration: data.user.focusBreakDuration || 5,
});
if (data.user.showTimeGrid !== undefined) setShowTimeGrid(data.user.showTimeGrid);
@ -3453,6 +3464,8 @@ function SettingsModal({
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.showSchedule !== undefined) setShowSchedule(data.user.showSchedule);
if (data.user.focusBreakDuration) setFocusBreakDuration(data.user.focusBreakDuration);
}
}
} catch (e) {
@ -3530,9 +3543,13 @@ function SettingsModal({
cellDuration: cellDuration,
viewStyle: viewStyle,
showNextTask: showNextTask,
showSomeday: showSomeday,
showAllDayEvents: showAllDay,
showSchedule: showSchedule,
headlineFont: headlineFont,
bodyFont: bodyFont,
fontWeight: fontWeight,
focusBreakDuration: focusBreakDuration,
password: (passwords.new && passwords.new.trim() !== "") ? passwords.new : undefined
})
});
@ -3545,25 +3562,31 @@ function SettingsModal({
// Update local app state
if (onSettingsChanged) {
onSettingsChanged({
showTimeGrid: profile.showTimeGrid !== undefined ? profile.showTimeGrid : true,
cellDuration: (profile.cellDuration || 30) as CellDuration,
viewStyle: (profile.viewStyle || 'list') as 'grid' | 'list',
showTimeGrid: showTimeGrid,
cellDuration: cellDuration,
viewStyle: viewStyle,
language: profile.language || 'en',
dateFormat: profile.dateFormat || 'MM/dd/yyyy',
timeFormat: profile.timeFormat || '12h',
startHour: profile.startHour || 8,
endHour: profile.endHour || 18,
fontSize: (profile.fontSize || 'M') as 'S' | 'M' | 'L',
fontSize: fontSize,
showNextTask: showNextTask,
headlineFont: profile.headlineFont || 'Inter',
bodyFont: profile.bodyFont || 'Inter',
fontWeight: profile.fontWeight || '400'
showSomeday: showSomeday,
showAllDayEvents: showAllDay,
showSchedule: showSchedule,
headlineFont: headlineFont,
bodyFont: bodyFont,
fontWeight: fontWeight
});
}
if (profile.focusTimerDuration && setFocusTimerDuration) {
setFocusTimerDuration(profile.focusTimerDuration);
}
if (profile.focusBreakDuration && setFocusBreakDuration) {
setFocusBreakDuration(profile.focusBreakDuration);
}
// Temporary success message
setTimeout(() => setAccountMsg(''), 3000);
@ -3597,44 +3620,16 @@ function SettingsModal({
};
return (
<>
<div
className="weekly-settings-overlay"
onClick={onClose}
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden'
}}
>
<div
className="weekly-settings-modal"
onClick={e => e.stopPropagation()}
style={{
position: 'relative',
width: `${modalSize.width}px`,
height: `${modalSize.height}px`,
transform: `translate(${modalPos.x}px, ${modalPos.y}px)`,
display: 'flex',
flexDirection: 'column',
maxHeight: '90vh',
maxWidth: '95vw',
resize: 'none'
}}
>
<header
className="weekly-settings-header"
style={{ cursor: isDragging ? 'grabbing' : 'grab' }}
onMouseDown={(e) => {
setIsDragging(true);
setDragStart({
x: e.clientX - modalPos.x,
y: e.clientY - modalPos.y
});
}}
>
className={`weekly-settings-overlay ${isVisible ? 'show' : ''}`}
onClick={handleClose}
style={{ zIndex: 1999 }}
/>
<div className={`weekly-settings-sidebar ${isVisible ? 'open' : ''}`}>
<header className="weekly-settings-header">
<h2 className="weekly-settings-title">{t.settings}</h2>
<button className="weekly-settings-close" onClick={onClose}>×</button>
<button className="weekly-settings-close" onClick={handleClose}>×</button>
</header>
<div className="weekly-settings-tabs" style={{ display: 'flex', borderBottom: '1px solid #eee', padding: '0 24px' }}>
@ -3658,7 +3653,7 @@ function SettingsModal({
</button>
</div>
<div className="weekly-settings-content">
<div className="weekly-settings-content" style={{ flex: 1, overflowY: 'auto', padding: '24px' }}>
{activeTab === 'general' ? (
<div style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
{/* Motto */}
@ -3701,6 +3696,19 @@ function SettingsModal({
</label>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<input
type="checkbox"
id="showSchedule"
checked={showSchedule}
onChange={e => setShowSchedule(e.target.checked)}
style={{ width: '16px', height: '16px' }}
/>
<label htmlFor="showSchedule" style={{ fontSize: '0.9rem', fontWeight: 600 }}>
Show Schedule / Calendar
</label>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<input
type="checkbox"
@ -3934,10 +3942,7 @@ function SettingsModal({
{FONT_WEIGHTS.map(weight => (
<div key={weight.value} style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
<input
type="checkbox" // Using checkbox as requested, acting like radio here for simplicity or actual radio? User asked for checkboxes instead of radio.
// But weight is mutually exclusive. Using check behavior where clicking one checks it and likely unchecks others if we want strict single selection, or just let standard radio behavior handle it but style it?
// User said "instead of radio buttons use checkboxes".
// I'll use input type="checkbox" but manage state to ensure single selection if needed, or just let opacity/style indicate selection.
type="checkbox"
checked={profile.fontWeight === weight.value}
onChange={() => setProfile({ ...profile, fontWeight: weight.value })}
style={{ width: '16px', height: '16px', cursor: 'pointer' }}
@ -3992,8 +3997,9 @@ function SettingsModal({
</label>
</div>
<div style={{ marginBottom: '1rem' }}>
<label style={{ display: 'block', marginBottom: '0.5rem', fontWeight: 500 }}>Focus Timer Duration (minutes)</label>
<div style={{ marginBottom: '1rem', display: 'flex', gap: '16px' }}>
<div style={{ flex: 1 }}>
<label style={{ display: 'block', marginBottom: '0.5rem', fontWeight: 500 }}>Focus Timer (min)</label>
<input
type="number"
min="1"
@ -4003,9 +4009,21 @@ function SettingsModal({
style={{ width: '100%', padding: '8px', border: '1px solid #ddd', borderRadius: '4px' }}
/>
</div>
<div style={{ flex: 1 }}>
<label style={{ display: 'block', marginBottom: '0.5rem', fontWeight: 500 }}>Focus Break (min)</label>
<input
type="number"
min="1"
max="60"
value={profile.focusBreakDuration || 5}
onChange={(e) => setProfile({ ...profile, focusBreakDuration: parseInt(e.target.value) || 5 })}
style={{ width: '100%', padding: '8px', border: '1px solid #ddd', borderRadius: '4px' }}
/>
</div>
</div>
<button
onClick={handleUpdateProfile} // Reuse handleUpdateProfile to save
onClick={handleUpdateProfile}
className="weekly-btn-primary"
style={{ marginTop: '8px', padding: '10px', alignSelf: 'flex-start' }}
>
@ -4090,8 +4108,6 @@ function SettingsModal({
</>
)
) : (
/* Account Tab */
/* Account Tab */
/* Account Tab */
<div style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
<form onSubmit={handleUpdateProfile} style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
@ -4216,30 +4232,8 @@ function SettingsModal({
</div>
)}
</div>
{/* Resize Handle */}
<div
style={{
position: 'absolute',
bottom: 0,
right: 0,
width: '20px',
height: '20px',
cursor: 'nwse-resize',
zIndex: 100
}}
onMouseDown={(e) => {
e.stopPropagation();
setIsResizing(true);
setResizeStart({
w: modalSize.width,
h: modalSize.height,
x: e.clientX,
y: e.clientY
});
}}
/>
</div>
</div>
</>
);
}