feat(web): make settings tabs URL-addressable via ?tab=

Add an optional `tab` search param (validated against the known tab set)
to /dashboard/settings and drive the Tabs component from it, so each tab
is shareable/bookmarkable (e.g. /dashboard/settings?tab=referral). Tab
clicks update the URL with replace to avoid history spam; existing links
need no search param since `tab` is optional.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-26 23:23:44 +03:00
parent 681a25ec2e
commit d679907e2c
2 changed files with 32 additions and 3 deletions

View File

@@ -35,7 +35,13 @@ import {
import { useEffect, useState } from "react";
import { ChangelogTab } from "./changelog-tab";
export function SettingsContent() {
export function SettingsContent({
tab,
onTabChange,
}: {
tab: string;
onTabChange: (next: string) => void;
}) {
const { t } = useTranslation();
const { user, signOut } = useAuth();
@@ -161,7 +167,7 @@ export function SettingsContent() {
<div className="mx-auto max-w-3xl space-y-6">
<h2 className="text-2xl font-bold">{t("settings.title")}</h2>
<Tabs defaultValue="profile">
<Tabs value={tab} onValueChange={onTabChange}>
<TabsList className="w-full flex-wrap">
<TabsTrigger value="profile" className="gap-2">
<User className="h-4 w-4" />

View File

@@ -8,11 +8,31 @@ const SettingsContent = lazy(() =>
})),
);
export const SETTINGS_TABS = [
"profile",
"security",
"connections",
"referral",
"account",
"changelog",
] as const;
export type SettingsTab = (typeof SETTINGS_TABS)[number];
function isSettingsTab(value: unknown): value is SettingsTab {
return SETTINGS_TABS.includes(value as SettingsTab);
}
export const Route = createFileRoute("/dashboard/settings")({
// `tab` is optional so existing <Link to="/dashboard/settings"> need no search param.
validateSearch: (search: Record<string, unknown>): { tab?: SettingsTab } =>
isSettingsTab(search.tab) ? { tab: search.tab } : {},
component: SettingsPage,
});
function SettingsPage() {
const { tab } = Route.useSearch();
const navigate = Route.useNavigate();
return (
<Suspense
fallback={
@@ -23,7 +43,10 @@ function SettingsPage() {
</div>
}
>
<SettingsContent />
<SettingsContent
tab={tab ?? "profile"}
onTabChange={(next) => navigate({ search: { tab: next as SettingsTab }, replace: true })}
/>
</Suspense>
);
}