From 1f1d584eeac1a12e51c2f83f1d272380d9fb03ee Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sun, 8 Feb 2026 15:54:02 +0100 Subject: [PATCH] refactor: settings --- .../modules/settings/routes/settings.tsx | 42 +++++++------------ app/routeTree.gen.ts | 21 ++++++++++ app/routes/(dashboard)/route.tsx | 2 +- app/routes/(dashboard)/settings/index.tsx | 19 +++++++++ 4 files changed, 56 insertions(+), 28 deletions(-) create mode 100644 app/routes/(dashboard)/settings/index.tsx diff --git a/app/client/modules/settings/routes/settings.tsx b/app/client/modules/settings/routes/settings.tsx index 21832daa..5109a746 100644 --- a/app/client/modules/settings/routes/settings.tsx +++ b/app/client/modules/settings/routes/settings.tsx @@ -1,7 +1,6 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { Download, KeyRound, User, X, Users, Settings as SettingsIcon } from "lucide-react"; import { useState } from "react"; -import { useNavigate, useSearchParams } from "react-router"; import { toast } from "sonner"; import { downloadResticPasswordMutation, @@ -24,31 +23,20 @@ import { Label } from "~/client/components/ui/label"; import { Switch } from "~/client/components/ui/switch"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/client/components/ui/tabs"; import { authClient } from "~/client/lib/auth-client"; -import { appContext } from "~/context"; +import { type AppContext } from "~/context"; import { TwoFactorSection } from "../components/two-factor-section"; import { UserManagement } from "../components/user-management"; -import type { Route } from "./+types/settings"; +import { useNavigate, useSearch } from "@tanstack/react-router"; export const handle = { breadcrumb: () => [{ label: "Settings" }], }; -export function meta(_: Route.MetaArgs) { - return [ - { title: "Zerobyte - Settings" }, - { - name: "description", - content: "Manage your account settings and preferences.", - }, - ]; -} +type Props = { + appContext: AppContext; +}; -export async function clientLoader({ context }: Route.LoaderArgs) { - const ctx = context.get(appContext); - return ctx; -} - -export default function Settings({ loaderData }: Route.ComponentProps) { +export function SettingsPage({ appContext }: Props) { const [currentPassword, setCurrentPassword] = useState(""); const [newPassword, setNewPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); @@ -56,11 +44,11 @@ export default function Settings({ loaderData }: Route.ComponentProps) { const [downloadPassword, setDownloadPassword] = useState(""); const [isChangingPassword, setIsChangingPassword] = useState(false); - const [searchParams, setSearchParams] = useSearchParams(); - const activeTab = searchParams.get("tab") || "account"; + const { tab } = useSearch({ from: "/(dashboard)/settings/" }); + const activeTab = tab || "account"; const navigate = useNavigate(); - const isAdmin = loaderData.user?.role === "admin"; + const isAdmin = appContext.user?.role === "admin"; const registrationStatusQuery = useQuery({ ...getRegistrationStatusOptions(), @@ -84,7 +72,7 @@ export default function Settings({ loaderData }: Route.ComponentProps) { await authClient.signOut({ fetchOptions: { onSuccess: () => { - void navigate("/login", { replace: true }); + void navigate({ to: "/login", replace: true }); }, onError: ({ error }) => { console.error(error); @@ -118,7 +106,7 @@ export default function Settings({ loaderData }: Route.ComponentProps) { }, }); - const handleChangePassword = async (e: React.FormEvent) => { + const handleChangePassword = async (e: React.SubmitEvent) => { e.preventDefault(); if (newPassword !== confirmPassword) { @@ -157,7 +145,7 @@ export default function Settings({ loaderData }: Route.ComponentProps) { }); }; - const handleDownloadResticPassword = (e: React.FormEvent) => { + const handleDownloadResticPassword = (e: React.SubmitEvent) => { e.preventDefault(); if (!downloadPassword) { @@ -173,7 +161,7 @@ export default function Settings({ loaderData }: Route.ComponentProps) { }; const onTabChange = (value: string) => { - setSearchParams({ tab: value }); + navigate({ to: ".", search: () => ({ tab: value }) }); }; return ( @@ -198,7 +186,7 @@ export default function Settings({ loaderData }: Route.ComponentProps) {
- +
@@ -318,7 +306,7 @@ export default function Settings({ loaderData }: Route.ComponentProps) { - + diff --git a/app/routeTree.gen.ts b/app/routeTree.gen.ts index 5bf4e5a5..59e72d76 100644 --- a/app/routeTree.gen.ts +++ b/app/routeTree.gen.ts @@ -15,6 +15,7 @@ import { Route as authOnboardingRouteImport } from './routes/(auth)/onboarding' import { Route as authLoginRouteImport } from './routes/(auth)/login' import { Route as authDownloadRecoveryKeyRouteImport } from './routes/(auth)/download-recovery-key' import { Route as dashboardVolumesIndexRouteImport } from './routes/(dashboard)/volumes/index' +import { Route as dashboardSettingsIndexRouteImport } from './routes/(dashboard)/settings/index' import { Route as dashboardRepositoriesIndexRouteImport } from './routes/(dashboard)/repositories/index' import { Route as dashboardNotificationsIndexRouteImport } from './routes/(dashboard)/notifications/index' import { Route as dashboardBackupsIndexRouteImport } from './routes/(dashboard)/backups/index' @@ -59,6 +60,11 @@ const dashboardVolumesIndexRoute = dashboardVolumesIndexRouteImport.update({ path: '/volumes/', getParentRoute: () => dashboardRouteRoute, } as any) +const dashboardSettingsIndexRoute = dashboardSettingsIndexRouteImport.update({ + id: '/settings/', + path: '/settings/', + getParentRoute: () => dashboardRouteRoute, +} as any) const dashboardRepositoriesIndexRoute = dashboardRepositoriesIndexRouteImport.update({ id: '/repositories/', @@ -157,6 +163,7 @@ export interface FileRoutesByFullPath { '/backups/': typeof dashboardBackupsIndexRoute '/notifications/': typeof dashboardNotificationsIndexRoute '/repositories/': typeof dashboardRepositoriesIndexRoute + '/settings/': typeof dashboardSettingsIndexRoute '/volumes/': typeof dashboardVolumesIndexRoute '/repositories/$repoId/$snapshotId': typeof dashboardRepositoriesRepoIdSnapshotIdRoute '/backups/$backupId/$snapshotId/restore': typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute @@ -178,6 +185,7 @@ export interface FileRoutesByTo { '/backups': typeof dashboardBackupsIndexRoute '/notifications': typeof dashboardNotificationsIndexRoute '/repositories': typeof dashboardRepositoriesIndexRoute + '/settings': typeof dashboardSettingsIndexRoute '/volumes': typeof dashboardVolumesIndexRoute '/repositories/$repoId/$snapshotId': typeof dashboardRepositoriesRepoIdSnapshotIdRoute '/backups/$backupId/$snapshotId/restore': typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute @@ -201,6 +209,7 @@ export interface FileRoutesById { '/(dashboard)/backups/': typeof dashboardBackupsIndexRoute '/(dashboard)/notifications/': typeof dashboardNotificationsIndexRoute '/(dashboard)/repositories/': typeof dashboardRepositoriesIndexRoute + '/(dashboard)/settings/': typeof dashboardSettingsIndexRoute '/(dashboard)/volumes/': typeof dashboardVolumesIndexRoute '/(dashboard)/repositories/$repoId/$snapshotId': typeof dashboardRepositoriesRepoIdSnapshotIdRoute '/(dashboard)/backups/$backupId/$snapshotId/restore': typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute @@ -224,6 +233,7 @@ export interface FileRouteTypes { | '/backups/' | '/notifications/' | '/repositories/' + | '/settings/' | '/volumes/' | '/repositories/$repoId/$snapshotId' | '/backups/$backupId/$snapshotId/restore' @@ -245,6 +255,7 @@ export interface FileRouteTypes { | '/backups' | '/notifications' | '/repositories' + | '/settings' | '/volumes' | '/repositories/$repoId/$snapshotId' | '/backups/$backupId/$snapshotId/restore' @@ -267,6 +278,7 @@ export interface FileRouteTypes { | '/(dashboard)/backups/' | '/(dashboard)/notifications/' | '/(dashboard)/repositories/' + | '/(dashboard)/settings/' | '/(dashboard)/volumes/' | '/(dashboard)/repositories/$repoId/$snapshotId' | '/(dashboard)/backups/$backupId/$snapshotId/restore' @@ -325,6 +337,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof dashboardVolumesIndexRouteImport parentRoute: typeof dashboardRouteRoute } + '/(dashboard)/settings/': { + id: '/(dashboard)/settings/' + path: '/settings' + fullPath: '/settings/' + preLoaderRoute: typeof dashboardSettingsIndexRouteImport + parentRoute: typeof dashboardRouteRoute + } '/(dashboard)/repositories/': { id: '/(dashboard)/repositories/' path: '/repositories' @@ -438,6 +457,7 @@ interface dashboardRouteRouteChildren { dashboardBackupsIndexRoute: typeof dashboardBackupsIndexRoute dashboardNotificationsIndexRoute: typeof dashboardNotificationsIndexRoute dashboardRepositoriesIndexRoute: typeof dashboardRepositoriesIndexRoute + dashboardSettingsIndexRoute: typeof dashboardSettingsIndexRoute dashboardVolumesIndexRoute: typeof dashboardVolumesIndexRoute dashboardRepositoriesRepoIdSnapshotIdRoute: typeof dashboardRepositoriesRepoIdSnapshotIdRoute dashboardBackupsBackupIdSnapshotIdRestoreRoute: typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute @@ -458,6 +478,7 @@ const dashboardRouteRouteChildren: dashboardRouteRouteChildren = { dashboardBackupsIndexRoute: dashboardBackupsIndexRoute, dashboardNotificationsIndexRoute: dashboardNotificationsIndexRoute, dashboardRepositoriesIndexRoute: dashboardRepositoriesIndexRoute, + dashboardSettingsIndexRoute: dashboardSettingsIndexRoute, dashboardVolumesIndexRoute: dashboardVolumesIndexRoute, dashboardRepositoriesRepoIdSnapshotIdRoute: dashboardRepositoriesRepoIdSnapshotIdRoute, diff --git a/app/routes/(dashboard)/route.tsx b/app/routes/(dashboard)/route.tsx index 86be3267..2b8a73e3 100644 --- a/app/routes/(dashboard)/route.tsx +++ b/app/routes/(dashboard)/route.tsx @@ -6,7 +6,7 @@ import type { AppContext } from "~/context"; import { authMiddleware } from "~/middleware/auth"; import { auth } from "~/server/lib/auth"; -const fetchUser = createServerFn({ method: "GET" }).handler(async () => { +export const fetchUser = createServerFn({ method: "GET" }).handler(async () => { const headers = getRequestHeaders(); const session = await auth.api.getSession({ headers }); diff --git a/app/routes/(dashboard)/settings/index.tsx b/app/routes/(dashboard)/settings/index.tsx new file mode 100644 index 00000000..d2284fae --- /dev/null +++ b/app/routes/(dashboard)/settings/index.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { fetchUser } from "../route"; +import type { AppContext } from "~/context"; +import { SettingsPage } from "~/client/modules/settings/routes/settings"; +import { type } from "arktype"; + +export const Route = createFileRoute("/(dashboard)/settings/")({ + component: RouteComponent, + validateSearch: type({ tab: "string?" }), + loader: async () => { + const authContext = await fetchUser(); + return authContext as AppContext; + }, +}); + +function RouteComponent() { + const appContext = Route.useLoaderData(); + return ; +}