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 (
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.