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 ;
+}