From 7e5e0124349419a34e8068441dc05aa7c48b2423 Mon Sep 17 00:00:00 2001 From: null Date: Sun, 5 Jul 2026 16:03:01 -0500 Subject: [PATCH] feat(settings): default landing page (plan Tier 6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New per-user 'default_landing_page' setting (tracker default / calendar / summary / spending), registered in the whitelist + defaults. - Implemented as a post-login redirect (keeps /=Tracker so the Home link and direct nav are unaffected). Precedence: admin → deep-link they were bounced from (state.from) → default landing → /. Resolved by fetching settings in the login success path, with a safe / fallback so a fetch failure never blocks login. - Settings: Select in the Regional section. Co-Authored-By: Claude Opus 4.8 --- client/pages/LoginPage.tsx | 31 +++++++++++++++++++++++++++---- client/pages/SettingsPage.tsx | 17 +++++++++++++++++ services/userSettings.js | 2 ++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/client/pages/LoginPage.tsx b/client/pages/LoginPage.tsx index a4c0224..8d6e891 100644 --- a/client/pages/LoginPage.tsx +++ b/client/pages/LoginPage.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { Link, useNavigate } from 'react-router-dom'; +import { Link, useNavigate, useLocation } from 'react-router-dom'; import { toast } from 'sonner'; import { api } from '@/api'; import { errMessage } from '@/lib/utils'; @@ -22,8 +22,13 @@ interface AuthModeInfo { oidc_provider_name?: string; } +const LANDING_ROUTES: Record = { + tracker: '/', calendar: '/calendar', summary: '/summary', spending: '/spending', +}; + export default function LoginPage() { const navigate = useNavigate(); + const location = useLocation(); const { setUser, refresh } = useAuth(); const [username, setUsername] = useState(''); @@ -49,6 +54,24 @@ export default function LoginPage() { const destFor = (user?: User | null) => (user?.is_default_admin ? '/admin' : '/'); + // Where to send the user after signing in. Precedence: admin console → + // explicit deep-link they were bounced from → their default landing page → /. + const resolveDest = async (user?: User | null): Promise => { + if (user?.is_default_admin) return '/admin'; + const from = (location.state as { from?: { pathname?: string } } | null)?.from?.pathname; + if (from && from !== '/login') return from; + try { + const s = (await api.settings()) as Record; + const landing = typeof s.default_landing_page === 'string' ? LANDING_ROUTES[s.default_landing_page] : undefined; + return landing || '/'; + } catch { + return '/'; // never block login on a settings-fetch failure + } + }; + const navigateAfterAuth = async (user?: User | null) => { + navigate(await resolveDest(user), { replace: true }); + }; + useEffect(() => { api.authMode() .then(raw => { @@ -77,7 +100,7 @@ export default function LoginPage() { setShowPrivacy(true); setShowChangePw(false); } else { - navigate(destFor(user), { replace: true }); + void navigateAfterAuth(user); } }; @@ -148,7 +171,7 @@ export default function LoginPage() { if (pendingUser?.first_login) { setShowPrivacy(true); } else { - navigate(destFor(pendingUser), { replace: true }); + void navigateAfterAuth(pendingUser); } } catch (err) { toast.error(errMessage(err, 'Failed to change password.')); @@ -164,7 +187,7 @@ export default function LoginPage() { refresh(); setShowPrivacy(false); - navigate(destFor(pendingUser), { replace: true }); + void navigateAfterAuth(pendingUser); }; return ( diff --git a/client/pages/SettingsPage.tsx b/client/pages/SettingsPage.tsx index 33deb62..ee330e3 100644 --- a/client/pages/SettingsPage.tsx +++ b/client/pages/SettingsPage.tsx @@ -315,6 +315,7 @@ interface SettingsState { currency: string; date_format: string; week_start: string; + default_landing_page: string; grace_period_days: number | string; due_soon_days: number | string; drift_threshold_pct: string; @@ -335,6 +336,7 @@ export default function SettingsPage() { currency: 'USD', date_format: 'MM/DD/YYYY', week_start: '0', + default_landing_page: 'tracker', grace_period_days: 5, due_soon_days: 3, drift_threshold_pct: '5', @@ -368,6 +370,7 @@ export default function SettingsPage() { currency: s.currency, date_format: s.date_format, week_start: s.week_start, + default_landing_page: s.default_landing_page, grace_period_days: s.grace_period_days, due_soon_days: s.due_soon_days, drift_threshold_pct: s.drift_threshold_pct, @@ -494,6 +497,20 @@ export default function SettingsPage() { + + + + {/* Tracker Layout */} diff --git a/services/userSettings.js b/services/userSettings.js index 455979b..8e7a93d 100644 --- a/services/userSettings.js +++ b/services/userSettings.js @@ -6,6 +6,7 @@ const USER_SETTING_KEYS = [ 'currency', 'date_format', 'week_start', + 'default_landing_page', 'grace_period_days', 'due_soon_days', 'notify_days_before', @@ -32,6 +33,7 @@ const USER_SETTING_KEYS = [ const USER_SETTING_DEFAULTS = { week_start: '0', + default_landing_page: 'tracker', due_soon_days: '3', search_bars_collapsed: 'false', bank_auto_categorize_merchants: 'true',