feat: persist view style and start-on preference per device

viewStyle, showTimeGrid, and startDayOffset now also write to a device
cookie so each device remembers its own view independently. Cookie
wins over DB on load, giving phone/desktop their own default view
without losing the cross-device DB fallback.

- DEVICE_ALSO_COOKIE_KEYS: viewStyle, showTimeGrid, startDayOffset
- saveSetting writes cookie for these in addition to DB
- viewStyle and showTimeGrid lazy-init from cookie on first render
- Both fetchProfile and fetchUserInfo apply cookie overrides last

v1.94.1
This commit is contained in:
mARTin 2026-04-07 23:29:35 +02:00
parent 0b40974b88
commit 075ccdba04
2 changed files with 37 additions and 3 deletions

View File

@ -1,6 +1,6 @@
{
"name": "my-weekly-todo-list",
"version": "1.94.0",
"version": "1.94.1",
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
"main": "index.js",
"scripts": {

View File

@ -117,6 +117,9 @@ const DEVICE_SETTINGS_KEYS = ["viewDays", "cellDuration", "startHour", "endHour"
const DEVICE_VIEW_SETTINGS_KEYS = ["showSomeday", "showAllDayEvents", "showTaskCheckboxes", "showProjectIcons", "weatherEnabled"] as const;
type DeviceViewSettingKey = typeof DEVICE_VIEW_SETTINGS_KEYS[number];
// Settings that save to DB (cross-device default) AND to cookie (device override wins on load)
const DEVICE_ALSO_COOKIE_KEYS = ["viewStyle", "showTimeGrid", "startDayOffset"];
function getCookie(name: string): string | null {
if (typeof document === "undefined") return null;
const match = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`));
@ -999,7 +1002,10 @@ export default function WeeklyView() {
};
const [cellDuration, setCellDuration] = useState<CellDuration>(30);
const [draggedTask, setDraggedTask] = useState<Task | null>(null);
const [showTimeGrid, setShowTimeGrid] = useState(true);
const [showTimeGrid, setShowTimeGrid] = useState<boolean>(() => {
const c = getCookie("setting_showTimeGrid");
return c !== null ? c === "true" : true;
});
const [slideDirection, setSlideDirection] = useState<"next" | "prev" | null>(
null,
);
@ -1018,7 +1024,10 @@ export default function WeeklyView() {
listId?: string;
slotIdx?: number;
} | null>(null);
const [viewStyle, setViewStyle] = useState<ViewStyle>("simple");
const [viewStyle, setViewStyle] = useState<ViewStyle>(() => {
const c = getCookie("setting_viewStyle");
return (c as ViewStyle) || "simple";
});
// Per-view settings: overrides that apply only to a specific view
type PerViewOverrides = {
@ -1407,6 +1416,12 @@ export default function WeeklyView() {
if (_cStart) setStartHour(Number(_cStart));
const _cEnd = getCookie("setting_endHour");
if (_cEnd) setEndHour(Number(_cEnd));
const _cVS = getCookie("setting_viewStyle");
if (_cVS) { setViewStyle(_cVS as ViewStyle); setProfile((p: any) => ({ ...p, viewStyle: _cVS })); }
const _cTG = getCookie("setting_showTimeGrid");
if (_cTG !== null) { const v = _cTG === "true"; setShowTimeGrid(v); setProfile((p: any) => ({ ...p, showTimeGrid: v })); }
const _cSDO = getCookie("setting_startDayOffset");
if (_cSDO !== null) setProfile((p: any) => ({ ...p, startDayOffset: Number(_cSDO) }));
// Show onboarding wizard for new users
if (profileData.hasCompletedOnboarding === false) {
@ -2271,6 +2286,10 @@ export default function WeeklyView() {
setCookie(`setting_${key}`, String(value));
return; // Don't write to DB — that would overwrite other devices
}
// Also persist to device cookie so this device remembers its own preference on reload
if (DEVICE_ALSO_COOKIE_KEYS.includes(key)) {
setCookie(`setting_${key}`, String(value));
}
// Keep profile object in sync so the debounced auto-save never sends stale values
setProfile((p: any) => ({ ...p, [key]: value }));
try {
@ -2499,6 +2518,21 @@ export default function WeeklyView() {
setCellDuration(data.user.cellDuration as CellDuration);
if (data.user.viewStyle)
setViewStyle(data.user.viewStyle as ViewStyle);
// Cookie overrides for settings that are also device-local
const _cVS2 = getCookie("setting_viewStyle");
if (_cVS2) { setViewStyle(_cVS2 as ViewStyle); setProfile((p: any) => ({ ...p, viewStyle: _cVS2, showTimeGrid: _cVS2 === "simple" || _cVS2 === "calendar" })); }
const _cTG2 = getCookie("setting_showTimeGrid");
if (_cTG2 !== null) setShowTimeGrid(_cTG2 === "true");
const _cSDO2 = getCookie("setting_startDayOffset");
if (_cSDO2 !== null) {
const offset = Number(_cSDO2);
setProfile((p: any) => ({ ...p, startDayOffset: offset }));
if (offset !== 0) {
const d = new Date(); d.setHours(0, 0, 0, 0); d.setDate(d.getDate() + offset);
setCurrentWeekStart(d);
}
}
}
}
} catch (e) {