import { useState } from "react"; import { useQuery, useMutation } from "@tanstack/react-query"; import { MainLayout } from "@/components/layouts/main-layout"; import { Organization, InsertOrganization } from "@shared/schema"; import { useAuth } from "@/hooks/use-auth"; import { useOrganization } from "@/context/organization-context"; import { apiRequest, queryClient } from "@/lib/queryClient"; import { useToast } from "@/hooks/use-toast"; import { useTheme } from "@/hooks/use-theme"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Tabs, TabsContent, TabsList, TabsTrigger, } from "@/components/ui/tabs"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Switch } from "@/components/ui/switch"; import { Separator } from "@/components/ui/separator"; import { Loader2, Moon, Sun, Lock, Laptop } from "lucide-react"; // Organization form schema const organizationFormSchema = z.object({ name: z.string().min(1, "Organization name is required"), isActive: z.boolean().default(true), }); // Create organization form schema const createOrganizationFormSchema = z.object({ name: z.string().min(1, "Organization name is required"), isActive: z.boolean().default(true), }); // Account form schema const accountFormSchema = z.object({ username: z.string().min(3, "Username must be at least 3 characters"), email: z.string().email("Please enter a valid email"), fullName: z.string().optional(), }); // Password form schema const passwordFormSchema = z.object({ currentPassword: z.string().min(1, "Current password is required"), newPassword: z.string().min(8, "New password must be at least 8 characters"), confirmPassword: z.string().min(8, "Please confirm your new password"), }).refine((data) => data.newPassword === data.confirmPassword, { message: "Passwords don't match", path: ["confirmPassword"], }); export default function SettingsPage() { const { toast } = useToast(); const { user } = useAuth(); const { theme, setTheme } = useTheme(); const { currentOrganization, setCurrentOrganization, createOrganizationMutation } = useOrganization(); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); // Create organization form const createOrganizationForm = useForm>({ resolver: zodResolver(createOrganizationFormSchema), defaultValues: { name: "", isActive: true, }, }); // Fetch organization data const { data: organization } = useQuery({ queryKey: ["/api/organizations", currentOrganization?.id], enabled: !!currentOrganization?.id, }); // Organization form const organizationForm = useForm>({ resolver: zodResolver(organizationFormSchema), defaultValues: { name: currentOrganization?.name || "", isActive: currentOrganization?.isActive, }, }); // Account form const accountForm = useForm>({ resolver: zodResolver(accountFormSchema), defaultValues: { username: user?.username || "", email: user?.email || "", fullName: user?.fullName || "", }, }); // Password form const passwordForm = useForm>({ resolver: zodResolver(passwordFormSchema), defaultValues: { currentPassword: "", newPassword: "", confirmPassword: "", }, }); // Update organization details when they change useState(() => { if (currentOrganization) { organizationForm.reset({ name: currentOrganization.name, isActive: currentOrganization.isActive, }); } }); // Update organization mutation const updateOrganizationMutation = useMutation({ mutationFn: async (data: z.infer) => { if (!currentOrganization) throw new Error("No organization selected"); const res = await apiRequest("PUT", `/api/organizations/${currentOrganization.id}`, data); return await res.json(); }, onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: ["/api/organizations"] }); queryClient.invalidateQueries({ queryKey: ["/api/organizations", currentOrganization?.id] }); // Update the current organization in context if (currentOrganization) { setCurrentOrganization({ ...currentOrganization, ...data, }); } toast({ title: "Organization updated", description: "The organization details have been updated successfully.", }); }, onError: (error) => { toast({ title: "Failed to update organization", description: error.message, variant: "destructive", }); }, }); // Update account mutation const updateAccountMutation = useMutation({ mutationFn: async (data: z.infer) => { if (!user) throw new Error("Not authenticated"); const res = await apiRequest("PUT", `/api/users/${user.id}`, data); return await res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/user"] }); toast({ title: "Account updated", description: "Your account details have been updated successfully.", }); }, onError: (error) => { toast({ title: "Failed to update account", description: error.message, variant: "destructive", }); }, }); // Change password mutation const changePasswordMutation = useMutation({ mutationFn: async (data: z.infer) => { if (!user) throw new Error("Not authenticated"); const res = await apiRequest("POST", "/api/change-password", { currentPassword: data.currentPassword, newPassword: data.newPassword, }); return await res.json(); }, onSuccess: () => { passwordForm.reset(); toast({ title: "Password changed", description: "Your password has been changed successfully.", }); }, onError: (error) => { toast({ title: "Failed to change password", description: error.message, variant: "destructive", }); }, }); // Delete organization mutation const deleteOrganizationMutation = useMutation({ mutationFn: async () => { if (!currentOrganization) throw new Error("No organization selected"); await apiRequest("DELETE", `/api/organizations/${currentOrganization.id}`); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/organizations"] }); setIsDeleteDialogOpen(false); toast({ title: "Organization deleted", description: "The organization has been deleted successfully.", }); }, onError: (error) => { toast({ title: "Failed to delete organization", description: error.message, variant: "destructive", }); }, }); // Form submission handlers const onOrganizationSubmit = (data: z.infer) => { updateOrganizationMutation.mutate(data); }; const onAccountSubmit = (data: z.infer) => { updateAccountMutation.mutate(data); }; const onPasswordSubmit = (data: z.infer) => { changePasswordMutation.mutate(data); }; // Check if current user has admin/manager permission const hasManagePermission = user?.role === "admin" || user?.role === "manager"; return ( Account Organization Appearance {/* Account Settings */} Account Information Update your personal account details.
( Username )} /> ( Email )} /> ( Full Name Your full name for display purposes )} />
Change Password Update your account password.
( Current Password )} /> ( New Password Must be at least 8 characters long )} /> ( Confirm New Password )} />
{/* Organization Settings */} {!hasManagePermission ? (

Access Restricted

You don't have permission to modify organization settings. Only administrators and managers can manage organizations.

) : ( <> {user?.role === "admin" && ( Create New Organization Add a new organization to your account
{ createOrganizationMutation.mutate(data); createOrganizationForm.reset(); })} className="space-y-4" > ( Organization Name )} /> (
Active Status Enable this organization upon creation
)} />
)} Organization Details Manage your organization information.
( Organization Name )} /> (
Active Status Enable or disable this organization
)} />
{user?.role === "admin" && ( Danger Zone Irreversible actions for your organization

Delete Organization

Permanently delete this organization and all associated data

)} )}
{/* Appearance Settings */} Appearance Customize the application appearance and theme.

Theme

Select your preferred theme appearance

setTheme("light")} >

Light

Light background with dark text

setTheme("dark")} >

Dark

Dark background with light text

setTheme("system")} >

System

Follow your system preference

{/* Delete Organization Dialog */} Delete Organization Are you sure you want to delete the organization {currentOrganization?.name}? This action cannot be undone. All domains, DNS records, and related data will be permanently deleted. Cancel deleteOrganizationMutation.mutate()} className="bg-destructive text-destructive-foreground hover:bg-destructive/90" disabled={deleteOrganizationMutation.isPending} > {deleteOrganizationMutation.isPending ? ( <> Deleting... ) : ( "Delete Organization" )}
); }