import { useMutation } from "@tanstack/react-query"; import { Download, Fingerprint, KeyRound, User, X, Settings as SettingsIcon, Building2 } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { downloadResticPasswordMutation } from "~/client/api-client/@tanstack/react-query.gen"; import type { GetOrgMembersResponse, GetSsoSettingsResponse } from "~/client/api-client/types.gen"; import { Button } from "~/client/components/ui/button"; import { Card, CardContent, CardDescription, CardTitle } from "~/client/components/ui/card"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "~/client/components/ui/dialog"; import { Input } from "~/client/components/ui/input"; import { Label } from "~/client/components/ui/label"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/client/components/ui/tabs"; import { authClient } from "~/client/lib/auth-client"; import { type AppContext } from "~/context"; import { TwoFactorSection } from "../components/two-factor-section"; import { useNavigate, useSearch } from "@tanstack/react-router"; import { SsoSettingsSection } from "~/client/modules/sso/components/sso-settings-section"; import { OrgMembersSection } from "../components/org-members-section"; import { useOrganizationContext } from "~/client/hooks/use-org-context"; type Props = { appContext: AppContext; initialMembers?: GetOrgMembersResponse; initialSsoSettings?: GetSsoSettingsResponse; initialOrigin?: string; }; export function SettingsPage({ appContext, initialMembers, initialSsoSettings, initialOrigin }: Props) { const [currentPassword, setCurrentPassword] = useState(""); const [newPassword, setNewPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [downloadDialogOpen, setDownloadDialogOpen] = useState(false); const [downloadPassword, setDownloadPassword] = useState(""); const [isChangingPassword, setIsChangingPassword] = useState(false); const { tab } = useSearch({ from: "/(dashboard)/settings/" }); const activeTab = tab || "account"; const navigate = useNavigate(); const { activeMember, activeOrganization } = useOrganizationContext(); const isOrgAdmin = activeMember?.role === "owner" || activeMember?.role === "admin"; const handleLogout = async () => { await authClient.signOut({ fetchOptions: { onSuccess: () => { void navigate({ to: "/login", replace: true }); }, onError: ({ error }) => { console.error(error); toast.error("Logout failed", { description: error.message }); }, }, }); }; const downloadResticPassword = useMutation({ ...downloadResticPasswordMutation(), onSuccess: (data) => { const blob = new Blob([data], { type: "text/plain" }); const url = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "restic.pass"; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); toast.success("Restic password file downloaded successfully"); setDownloadDialogOpen(false); setDownloadPassword(""); }, onError: (error) => { toast.error("Failed to download Restic password", { description: error.message, }); }, }); const handleChangePassword = async (e: React.SubmitEvent) => { e.preventDefault(); if (newPassword !== confirmPassword) { toast.error("Passwords do not match"); return; } if (newPassword.length < 8) { toast.error("Password must be at least 8 characters long"); return; } await authClient.changePassword({ newPassword, currentPassword: currentPassword, revokeOtherSessions: true, fetchOptions: { onSuccess: () => { toast.success("Password changed successfully. You will be logged out."); setTimeout(() => { void handleLogout(); }, 1500); }, onError: ({ error }) => { toast.error("Failed to change password", { description: error.message, }); }, onRequest: () => { setIsChangingPassword(true); }, onResponse: () => { setIsChangingPassword(false); }, }, }); }; const handleDownloadResticPassword = (e: React.SubmitEvent) => { e.preventDefault(); if (!downloadPassword) { toast.error("Password is required"); return; } downloadResticPassword.mutate({ body: { password: downloadPassword, }, }); }; const onTabChange = (value: string) => { void navigate({ to: ".", search: () => ({ tab: value }) }); }; return (
Account {isOrgAdmin && Organization}
Account Information Your account details
Change Password Update your password to keep your account secure
setCurrentPassword(e.target.value)} className="max-w-md" required />
setNewPassword(e.target.value)} className="max-w-md" required minLength={8} />

Must be at least 8 characters long

setConfirmPassword(e.target.value)} className="max-w-md" required minLength={8} />
Backup Recovery Key Download your recovery key for Restic backups

This file contains the encryption password used by Restic to secure your backups. Store it in a safe place (like a password manager or encrypted storage). If you lose access to this server, you'll need this file to recover your backup data.

Download Recovery Key For security reasons, please enter your account password to download the recovery key file.
setDownloadPassword(e.target.value)} placeholder="Enter your password" required />
{isOrgAdmin && (
Organization Details Reference details for the active organization
Members Manage organization members and roles
Single Sign-On Configure OIDC provider settings
)}
); }