refactor: settings
This commit is contained in:
parent
975ff23799
commit
1f1d584eea
4 changed files with 56 additions and 28 deletions
|
|
@ -1,7 +1,6 @@
|
||||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import { Download, KeyRound, User, X, Users, Settings as SettingsIcon } from "lucide-react";
|
import { Download, KeyRound, User, X, Users, Settings as SettingsIcon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useNavigate, useSearchParams } from "react-router";
|
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import {
|
import {
|
||||||
downloadResticPasswordMutation,
|
downloadResticPasswordMutation,
|
||||||
|
|
@ -24,31 +23,20 @@ import { Label } from "~/client/components/ui/label";
|
||||||
import { Switch } from "~/client/components/ui/switch";
|
import { Switch } from "~/client/components/ui/switch";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/client/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/client/components/ui/tabs";
|
||||||
import { authClient } from "~/client/lib/auth-client";
|
import { authClient } from "~/client/lib/auth-client";
|
||||||
import { appContext } from "~/context";
|
import { type AppContext } from "~/context";
|
||||||
import { TwoFactorSection } from "../components/two-factor-section";
|
import { TwoFactorSection } from "../components/two-factor-section";
|
||||||
import { UserManagement } from "../components/user-management";
|
import { UserManagement } from "../components/user-management";
|
||||||
import type { Route } from "./+types/settings";
|
import { useNavigate, useSearch } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const handle = {
|
export const handle = {
|
||||||
breadcrumb: () => [{ label: "Settings" }],
|
breadcrumb: () => [{ label: "Settings" }],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function meta(_: Route.MetaArgs) {
|
type Props = {
|
||||||
return [
|
appContext: AppContext;
|
||||||
{ title: "Zerobyte - Settings" },
|
};
|
||||||
{
|
|
||||||
name: "description",
|
|
||||||
content: "Manage your account settings and preferences.",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function clientLoader({ context }: Route.LoaderArgs) {
|
export function SettingsPage({ appContext }: Props) {
|
||||||
const ctx = context.get(appContext);
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Settings({ loaderData }: Route.ComponentProps) {
|
|
||||||
const [currentPassword, setCurrentPassword] = useState("");
|
const [currentPassword, setCurrentPassword] = useState("");
|
||||||
const [newPassword, setNewPassword] = useState("");
|
const [newPassword, setNewPassword] = useState("");
|
||||||
const [confirmPassword, setConfirmPassword] = useState("");
|
const [confirmPassword, setConfirmPassword] = useState("");
|
||||||
|
|
@ -56,11 +44,11 @@ export default function Settings({ loaderData }: Route.ComponentProps) {
|
||||||
const [downloadPassword, setDownloadPassword] = useState("");
|
const [downloadPassword, setDownloadPassword] = useState("");
|
||||||
const [isChangingPassword, setIsChangingPassword] = useState(false);
|
const [isChangingPassword, setIsChangingPassword] = useState(false);
|
||||||
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const { tab } = useSearch({ from: "/(dashboard)/settings/" });
|
||||||
const activeTab = searchParams.get("tab") || "account";
|
const activeTab = tab || "account";
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const isAdmin = loaderData.user?.role === "admin";
|
const isAdmin = appContext.user?.role === "admin";
|
||||||
|
|
||||||
const registrationStatusQuery = useQuery({
|
const registrationStatusQuery = useQuery({
|
||||||
...getRegistrationStatusOptions(),
|
...getRegistrationStatusOptions(),
|
||||||
|
|
@ -84,7 +72,7 @@ export default function Settings({ loaderData }: Route.ComponentProps) {
|
||||||
await authClient.signOut({
|
await authClient.signOut({
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
void navigate("/login", { replace: true });
|
void navigate({ to: "/login", replace: true });
|
||||||
},
|
},
|
||||||
onError: ({ error }) => {
|
onError: ({ error }) => {
|
||||||
console.error(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();
|
e.preventDefault();
|
||||||
|
|
||||||
if (newPassword !== confirmPassword) {
|
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();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!downloadPassword) {
|
if (!downloadPassword) {
|
||||||
|
|
@ -173,7 +161,7 @@ export default function Settings({ loaderData }: Route.ComponentProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const onTabChange = (value: string) => {
|
const onTabChange = (value: string) => {
|
||||||
setSearchParams({ tab: value });
|
navigate({ to: ".", search: () => ({ tab: value }) });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -198,7 +186,7 @@ export default function Settings({ loaderData }: Route.ComponentProps) {
|
||||||
<CardContent className="p-6 space-y-4">
|
<CardContent className="p-6 space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Username</Label>
|
<Label>Username</Label>
|
||||||
<Input value={loaderData.user?.username || ""} disabled className="max-w-md" />
|
<Input value={appContext.user?.username || ""} disabled className="max-w-md" />
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
||||||
|
|
@ -318,7 +306,7 @@ export default function Settings({ loaderData }: Route.ComponentProps) {
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
||||||
<TwoFactorSection twoFactorEnabled={loaderData.user?.twoFactorEnabled} />
|
<TwoFactorSection twoFactorEnabled={appContext.user?.twoFactorEnabled} />
|
||||||
</Card>
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import { Route as authOnboardingRouteImport } from './routes/(auth)/onboarding'
|
||||||
import { Route as authLoginRouteImport } from './routes/(auth)/login'
|
import { Route as authLoginRouteImport } from './routes/(auth)/login'
|
||||||
import { Route as authDownloadRecoveryKeyRouteImport } from './routes/(auth)/download-recovery-key'
|
import { Route as authDownloadRecoveryKeyRouteImport } from './routes/(auth)/download-recovery-key'
|
||||||
import { Route as dashboardVolumesIndexRouteImport } from './routes/(dashboard)/volumes/index'
|
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 dashboardRepositoriesIndexRouteImport } from './routes/(dashboard)/repositories/index'
|
||||||
import { Route as dashboardNotificationsIndexRouteImport } from './routes/(dashboard)/notifications/index'
|
import { Route as dashboardNotificationsIndexRouteImport } from './routes/(dashboard)/notifications/index'
|
||||||
import { Route as dashboardBackupsIndexRouteImport } from './routes/(dashboard)/backups/index'
|
import { Route as dashboardBackupsIndexRouteImport } from './routes/(dashboard)/backups/index'
|
||||||
|
|
@ -59,6 +60,11 @@ const dashboardVolumesIndexRoute = dashboardVolumesIndexRouteImport.update({
|
||||||
path: '/volumes/',
|
path: '/volumes/',
|
||||||
getParentRoute: () => dashboardRouteRoute,
|
getParentRoute: () => dashboardRouteRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
const dashboardSettingsIndexRoute = dashboardSettingsIndexRouteImport.update({
|
||||||
|
id: '/settings/',
|
||||||
|
path: '/settings/',
|
||||||
|
getParentRoute: () => dashboardRouteRoute,
|
||||||
|
} as any)
|
||||||
const dashboardRepositoriesIndexRoute =
|
const dashboardRepositoriesIndexRoute =
|
||||||
dashboardRepositoriesIndexRouteImport.update({
|
dashboardRepositoriesIndexRouteImport.update({
|
||||||
id: '/repositories/',
|
id: '/repositories/',
|
||||||
|
|
@ -157,6 +163,7 @@ export interface FileRoutesByFullPath {
|
||||||
'/backups/': typeof dashboardBackupsIndexRoute
|
'/backups/': typeof dashboardBackupsIndexRoute
|
||||||
'/notifications/': typeof dashboardNotificationsIndexRoute
|
'/notifications/': typeof dashboardNotificationsIndexRoute
|
||||||
'/repositories/': typeof dashboardRepositoriesIndexRoute
|
'/repositories/': typeof dashboardRepositoriesIndexRoute
|
||||||
|
'/settings/': typeof dashboardSettingsIndexRoute
|
||||||
'/volumes/': typeof dashboardVolumesIndexRoute
|
'/volumes/': typeof dashboardVolumesIndexRoute
|
||||||
'/repositories/$repoId/$snapshotId': typeof dashboardRepositoriesRepoIdSnapshotIdRoute
|
'/repositories/$repoId/$snapshotId': typeof dashboardRepositoriesRepoIdSnapshotIdRoute
|
||||||
'/backups/$backupId/$snapshotId/restore': typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute
|
'/backups/$backupId/$snapshotId/restore': typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute
|
||||||
|
|
@ -178,6 +185,7 @@ export interface FileRoutesByTo {
|
||||||
'/backups': typeof dashboardBackupsIndexRoute
|
'/backups': typeof dashboardBackupsIndexRoute
|
||||||
'/notifications': typeof dashboardNotificationsIndexRoute
|
'/notifications': typeof dashboardNotificationsIndexRoute
|
||||||
'/repositories': typeof dashboardRepositoriesIndexRoute
|
'/repositories': typeof dashboardRepositoriesIndexRoute
|
||||||
|
'/settings': typeof dashboardSettingsIndexRoute
|
||||||
'/volumes': typeof dashboardVolumesIndexRoute
|
'/volumes': typeof dashboardVolumesIndexRoute
|
||||||
'/repositories/$repoId/$snapshotId': typeof dashboardRepositoriesRepoIdSnapshotIdRoute
|
'/repositories/$repoId/$snapshotId': typeof dashboardRepositoriesRepoIdSnapshotIdRoute
|
||||||
'/backups/$backupId/$snapshotId/restore': typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute
|
'/backups/$backupId/$snapshotId/restore': typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute
|
||||||
|
|
@ -201,6 +209,7 @@ export interface FileRoutesById {
|
||||||
'/(dashboard)/backups/': typeof dashboardBackupsIndexRoute
|
'/(dashboard)/backups/': typeof dashboardBackupsIndexRoute
|
||||||
'/(dashboard)/notifications/': typeof dashboardNotificationsIndexRoute
|
'/(dashboard)/notifications/': typeof dashboardNotificationsIndexRoute
|
||||||
'/(dashboard)/repositories/': typeof dashboardRepositoriesIndexRoute
|
'/(dashboard)/repositories/': typeof dashboardRepositoriesIndexRoute
|
||||||
|
'/(dashboard)/settings/': typeof dashboardSettingsIndexRoute
|
||||||
'/(dashboard)/volumes/': typeof dashboardVolumesIndexRoute
|
'/(dashboard)/volumes/': typeof dashboardVolumesIndexRoute
|
||||||
'/(dashboard)/repositories/$repoId/$snapshotId': typeof dashboardRepositoriesRepoIdSnapshotIdRoute
|
'/(dashboard)/repositories/$repoId/$snapshotId': typeof dashboardRepositoriesRepoIdSnapshotIdRoute
|
||||||
'/(dashboard)/backups/$backupId/$snapshotId/restore': typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute
|
'/(dashboard)/backups/$backupId/$snapshotId/restore': typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute
|
||||||
|
|
@ -224,6 +233,7 @@ export interface FileRouteTypes {
|
||||||
| '/backups/'
|
| '/backups/'
|
||||||
| '/notifications/'
|
| '/notifications/'
|
||||||
| '/repositories/'
|
| '/repositories/'
|
||||||
|
| '/settings/'
|
||||||
| '/volumes/'
|
| '/volumes/'
|
||||||
| '/repositories/$repoId/$snapshotId'
|
| '/repositories/$repoId/$snapshotId'
|
||||||
| '/backups/$backupId/$snapshotId/restore'
|
| '/backups/$backupId/$snapshotId/restore'
|
||||||
|
|
@ -245,6 +255,7 @@ export interface FileRouteTypes {
|
||||||
| '/backups'
|
| '/backups'
|
||||||
| '/notifications'
|
| '/notifications'
|
||||||
| '/repositories'
|
| '/repositories'
|
||||||
|
| '/settings'
|
||||||
| '/volumes'
|
| '/volumes'
|
||||||
| '/repositories/$repoId/$snapshotId'
|
| '/repositories/$repoId/$snapshotId'
|
||||||
| '/backups/$backupId/$snapshotId/restore'
|
| '/backups/$backupId/$snapshotId/restore'
|
||||||
|
|
@ -267,6 +278,7 @@ export interface FileRouteTypes {
|
||||||
| '/(dashboard)/backups/'
|
| '/(dashboard)/backups/'
|
||||||
| '/(dashboard)/notifications/'
|
| '/(dashboard)/notifications/'
|
||||||
| '/(dashboard)/repositories/'
|
| '/(dashboard)/repositories/'
|
||||||
|
| '/(dashboard)/settings/'
|
||||||
| '/(dashboard)/volumes/'
|
| '/(dashboard)/volumes/'
|
||||||
| '/(dashboard)/repositories/$repoId/$snapshotId'
|
| '/(dashboard)/repositories/$repoId/$snapshotId'
|
||||||
| '/(dashboard)/backups/$backupId/$snapshotId/restore'
|
| '/(dashboard)/backups/$backupId/$snapshotId/restore'
|
||||||
|
|
@ -325,6 +337,13 @@ declare module '@tanstack/react-router' {
|
||||||
preLoaderRoute: typeof dashboardVolumesIndexRouteImport
|
preLoaderRoute: typeof dashboardVolumesIndexRouteImport
|
||||||
parentRoute: typeof dashboardRouteRoute
|
parentRoute: typeof dashboardRouteRoute
|
||||||
}
|
}
|
||||||
|
'/(dashboard)/settings/': {
|
||||||
|
id: '/(dashboard)/settings/'
|
||||||
|
path: '/settings'
|
||||||
|
fullPath: '/settings/'
|
||||||
|
preLoaderRoute: typeof dashboardSettingsIndexRouteImport
|
||||||
|
parentRoute: typeof dashboardRouteRoute
|
||||||
|
}
|
||||||
'/(dashboard)/repositories/': {
|
'/(dashboard)/repositories/': {
|
||||||
id: '/(dashboard)/repositories/'
|
id: '/(dashboard)/repositories/'
|
||||||
path: '/repositories'
|
path: '/repositories'
|
||||||
|
|
@ -438,6 +457,7 @@ interface dashboardRouteRouteChildren {
|
||||||
dashboardBackupsIndexRoute: typeof dashboardBackupsIndexRoute
|
dashboardBackupsIndexRoute: typeof dashboardBackupsIndexRoute
|
||||||
dashboardNotificationsIndexRoute: typeof dashboardNotificationsIndexRoute
|
dashboardNotificationsIndexRoute: typeof dashboardNotificationsIndexRoute
|
||||||
dashboardRepositoriesIndexRoute: typeof dashboardRepositoriesIndexRoute
|
dashboardRepositoriesIndexRoute: typeof dashboardRepositoriesIndexRoute
|
||||||
|
dashboardSettingsIndexRoute: typeof dashboardSettingsIndexRoute
|
||||||
dashboardVolumesIndexRoute: typeof dashboardVolumesIndexRoute
|
dashboardVolumesIndexRoute: typeof dashboardVolumesIndexRoute
|
||||||
dashboardRepositoriesRepoIdSnapshotIdRoute: typeof dashboardRepositoriesRepoIdSnapshotIdRoute
|
dashboardRepositoriesRepoIdSnapshotIdRoute: typeof dashboardRepositoriesRepoIdSnapshotIdRoute
|
||||||
dashboardBackupsBackupIdSnapshotIdRestoreRoute: typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute
|
dashboardBackupsBackupIdSnapshotIdRestoreRoute: typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute
|
||||||
|
|
@ -458,6 +478,7 @@ const dashboardRouteRouteChildren: dashboardRouteRouteChildren = {
|
||||||
dashboardBackupsIndexRoute: dashboardBackupsIndexRoute,
|
dashboardBackupsIndexRoute: dashboardBackupsIndexRoute,
|
||||||
dashboardNotificationsIndexRoute: dashboardNotificationsIndexRoute,
|
dashboardNotificationsIndexRoute: dashboardNotificationsIndexRoute,
|
||||||
dashboardRepositoriesIndexRoute: dashboardRepositoriesIndexRoute,
|
dashboardRepositoriesIndexRoute: dashboardRepositoriesIndexRoute,
|
||||||
|
dashboardSettingsIndexRoute: dashboardSettingsIndexRoute,
|
||||||
dashboardVolumesIndexRoute: dashboardVolumesIndexRoute,
|
dashboardVolumesIndexRoute: dashboardVolumesIndexRoute,
|
||||||
dashboardRepositoriesRepoIdSnapshotIdRoute:
|
dashboardRepositoriesRepoIdSnapshotIdRoute:
|
||||||
dashboardRepositoriesRepoIdSnapshotIdRoute,
|
dashboardRepositoriesRepoIdSnapshotIdRoute,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import type { AppContext } from "~/context";
|
||||||
import { authMiddleware } from "~/middleware/auth";
|
import { authMiddleware } from "~/middleware/auth";
|
||||||
import { auth } from "~/server/lib/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 headers = getRequestHeaders();
|
||||||
const session = await auth.api.getSession({ headers });
|
const session = await auth.api.getSession({ headers });
|
||||||
|
|
||||||
|
|
|
||||||
19
app/routes/(dashboard)/settings/index.tsx
Normal file
19
app/routes/(dashboard)/settings/index.tsx
Normal file
|
|
@ -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 <SettingsPage appContext={appContext} />;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue