From 1ff4082129092153885e3f76d974dd4c7f9ec4ae Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 30 Jun 2026 06:48:09 +0000 Subject: [PATCH] Give user settings sections their own URLs Each section of the user settings page (Profile, Account, Notifications, API Access, Data & Privacy) is now a real, linkable path (/settings/profile, /settings/account, etc.) instead of local toggle state, so sections can be bookmarked, shared, opened in a new tab, and reached via the browser back/forward buttons. - Route now matches an optional segment (settings/:section?), keeping the single route so the shared loader/action and form submissions are unchanged. An unknown section redirects back to /settings. - The shared settings nav components render proper s when given a buildHref/backHref, and keep their button/onClick behaviour for the league settings page (which has unsaved-changes guards). - The settings page derives the active section and mobile grid/section view from the URL param instead of useState. - Notifications "Account settings" link and the Discord OAuth callback now point at /settings/account. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Jt8Hhsio4bHGMaDZy7ftBs --- .../league/settings/SettingsNavigation.tsx | 157 ++++++++++++------ .../__tests__/SettingsNavigation.test.tsx | 67 ++++++++ .../user/settings/AccountSection.tsx | 2 +- .../user/settings/NotificationsSection.tsx | 12 +- .../__tests__/AccountSection.test.tsx | 2 +- app/routes.ts | 2 +- app/routes/__tests__/settings.test.ts | 56 +++++++ app/routes/settings.tsx | 32 ++-- 8 files changed, 251 insertions(+), 79 deletions(-) create mode 100644 app/components/league/settings/__tests__/SettingsNavigation.test.tsx create mode 100644 app/routes/__tests__/settings.test.ts diff --git a/app/components/league/settings/SettingsNavigation.tsx b/app/components/league/settings/SettingsNavigation.tsx index ec0a139..d6542bb 100644 --- a/app/components/league/settings/SettingsNavigation.tsx +++ b/app/components/league/settings/SettingsNavigation.tsx @@ -1,6 +1,7 @@ import type { ComponentType } from "react"; import type { LucideProps } from "lucide-react"; import { ArrowLeft } from "lucide-react"; +import { Link } from "react-router"; import { cn } from "~/lib/utils"; type SettingsNavSection = { @@ -17,10 +18,37 @@ export type SettingsGridSection = SettingsNavSection & { export function SettingsMobileGridNav({ sections, onSectionChange, + buildHref, }: { sections: readonly SettingsGridSection[]; - onSectionChange: (sectionId: string) => void; + onSectionChange?: (sectionId: string) => void; + buildHref?: (sectionId: string) => string; }) { + const cardClassName = (section: SettingsGridSection) => + cn( + "flex cursor-pointer flex-col gap-3 rounded-xl border bg-card p-4 text-left transition-colors hover:border-primary/40 active:scale-[0.98]", + section.isDanger && "border-destructive/40" + ); + + const cardInner = (section: SettingsGridSection) => ( + <> +
+ +
+
+

{section.label}

+

{section.subtitle}

+
+ + ); + return (
@@ -29,32 +57,22 @@ export function SettingsMobileGridNav({

- {sections.map((section) => ( -
-
-

{section.label}

-

{section.subtitle}

-
- - ))} + {cardInner(section)} + + ) + )}
); @@ -62,19 +80,30 @@ export function SettingsMobileGridNav({ export function SettingsMobileSectionPill({ onShowGrid, + backHref, }: { - onShowGrid: () => void; + onShowGrid?: () => void; + backHref?: string; }) { + const className = + "flex cursor-pointer items-center gap-1.5 rounded-full border bg-card px-3 py-1.5 text-sm font-medium hover:bg-muted"; + const content = ( + <> + + Back to all settings + + ); return (
- + {backHref ? ( + + {content} + + ) : ( + + )}
); } @@ -83,33 +112,57 @@ export function SettingsDesktopNav({ sections, activeSection, onSectionChange, + buildHref, + navLabel = "League settings", }: { sections: readonly SettingsGridSection[]; activeSection: string; - onSectionChange: (sectionId: string) => void; + onSectionChange?: (sectionId: string) => void; + buildHref?: (sectionId: string) => string; + navLabel?: string; }) { + const itemClassName = (section: SettingsGridSection) => + cn( + "flex w-full cursor-pointer items-center gap-2.5 rounded-md px-3 py-2 text-left text-sm font-medium text-muted-foreground hover:bg-muted hover:text-foreground", + activeSection === section.id && "bg-muted text-foreground" + ); + + const itemInner = (section: SettingsGridSection) => ( + <> +