mirror of
https://github.com/freedbygrace/DynamoDNS.git
synced 2026-08-10 10:16:54 +00:00
7823f21eb2
Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9111ef36-26c8-4085-84ca-a35dc1fec1b5 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7083d608-d6d3-4a6a-9a27-6286c5109627/582dd4fe-d503-4ab1-81ab-a04bba654cf7.jpg
711 lines
26 KiB
TypeScript
711 lines
26 KiB
TypeScript
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<z.infer<typeof createOrganizationFormSchema>>({
|
|
resolver: zodResolver(createOrganizationFormSchema),
|
|
defaultValues: {
|
|
name: "",
|
|
isActive: true,
|
|
},
|
|
});
|
|
|
|
// Fetch organization data
|
|
const { data: organization } = useQuery<Organization>({
|
|
queryKey: ["/api/organizations", currentOrganization?.id],
|
|
enabled: !!currentOrganization?.id,
|
|
});
|
|
|
|
// Organization form
|
|
const organizationForm = useForm<z.infer<typeof organizationFormSchema>>({
|
|
resolver: zodResolver(organizationFormSchema),
|
|
defaultValues: {
|
|
name: currentOrganization?.name || "",
|
|
isActive: currentOrganization?.isActive,
|
|
},
|
|
});
|
|
|
|
// Account form
|
|
const accountForm = useForm<z.infer<typeof accountFormSchema>>({
|
|
resolver: zodResolver(accountFormSchema),
|
|
defaultValues: {
|
|
username: user?.username || "",
|
|
email: user?.email || "",
|
|
fullName: user?.fullName || "",
|
|
},
|
|
});
|
|
|
|
// Password form
|
|
const passwordForm = useForm<z.infer<typeof passwordFormSchema>>({
|
|
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<typeof organizationFormSchema>) => {
|
|
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<typeof accountFormSchema>) => {
|
|
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<typeof passwordFormSchema>) => {
|
|
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<typeof organizationFormSchema>) => {
|
|
updateOrganizationMutation.mutate(data);
|
|
};
|
|
|
|
const onAccountSubmit = (data: z.infer<typeof accountFormSchema>) => {
|
|
updateAccountMutation.mutate(data);
|
|
};
|
|
|
|
const onPasswordSubmit = (data: z.infer<typeof passwordFormSchema>) => {
|
|
changePasswordMutation.mutate(data);
|
|
};
|
|
|
|
// Check if current user has admin/manager permission
|
|
const hasManagePermission = user?.role === "admin" || user?.role === "manager";
|
|
|
|
return (
|
|
<MainLayout
|
|
title="Settings"
|
|
description="Manage your account and organization settings."
|
|
>
|
|
<Tabs defaultValue="account" className="space-y-6">
|
|
<TabsList className="w-full sm:w-auto">
|
|
<TabsTrigger value="account">Account</TabsTrigger>
|
|
<TabsTrigger value="organization">Organization</TabsTrigger>
|
|
<TabsTrigger value="appearance">Appearance</TabsTrigger>
|
|
</TabsList>
|
|
|
|
{/* Account Settings */}
|
|
<TabsContent value="account" className="space-y-6">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Account Information</CardTitle>
|
|
<CardDescription>
|
|
Update your personal account details.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Form {...accountForm}>
|
|
<form onSubmit={accountForm.handleSubmit(onAccountSubmit)} className="space-y-4">
|
|
<FormField
|
|
control={accountForm.control}
|
|
name="username"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Username</FormLabel>
|
|
<FormControl>
|
|
<Input {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={accountForm.control}
|
|
name="email"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Email</FormLabel>
|
|
<FormControl>
|
|
<Input type="email" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={accountForm.control}
|
|
name="fullName"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Full Name</FormLabel>
|
|
<FormControl>
|
|
<Input {...field} value={field.value || ""} />
|
|
</FormControl>
|
|
<FormDescription>
|
|
Your full name for display purposes
|
|
</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<Button
|
|
type="submit"
|
|
disabled={updateAccountMutation.isPending}
|
|
>
|
|
{updateAccountMutation.isPending ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Saving...
|
|
</>
|
|
) : (
|
|
"Save Changes"
|
|
)}
|
|
</Button>
|
|
</form>
|
|
</Form>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Change Password</CardTitle>
|
|
<CardDescription>
|
|
Update your account password.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Form {...passwordForm}>
|
|
<form onSubmit={passwordForm.handleSubmit(onPasswordSubmit)} className="space-y-4">
|
|
<FormField
|
|
control={passwordForm.control}
|
|
name="currentPassword"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Current Password</FormLabel>
|
|
<FormControl>
|
|
<Input type="password" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={passwordForm.control}
|
|
name="newPassword"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>New Password</FormLabel>
|
|
<FormControl>
|
|
<Input type="password" {...field} />
|
|
</FormControl>
|
|
<FormDescription>
|
|
Must be at least 8 characters long
|
|
</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={passwordForm.control}
|
|
name="confirmPassword"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Confirm New Password</FormLabel>
|
|
<FormControl>
|
|
<Input type="password" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<Button
|
|
type="submit"
|
|
disabled={changePasswordMutation.isPending}
|
|
>
|
|
{changePasswordMutation.isPending ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Changing Password...
|
|
</>
|
|
) : (
|
|
"Change Password"
|
|
)}
|
|
</Button>
|
|
</form>
|
|
</Form>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
|
|
{/* Organization Settings */}
|
|
<TabsContent value="organization" className="space-y-6">
|
|
{!hasManagePermission ? (
|
|
<Card>
|
|
<CardContent className="pt-6 flex flex-col items-center justify-center h-64">
|
|
<Lock className="h-12 w-12 text-muted-foreground mb-4" />
|
|
<h3 className="text-lg font-medium mb-2">Access Restricted</h3>
|
|
<p className="text-center text-muted-foreground">
|
|
You don't have permission to modify organization settings. Only administrators and managers can manage organizations.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
) : (
|
|
<>
|
|
{user?.role === "admin" && (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Create New Organization</CardTitle>
|
|
<CardDescription>
|
|
Add a new organization to your account
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Form {...createOrganizationForm}>
|
|
<form
|
|
onSubmit={createOrganizationForm.handleSubmit((data) => {
|
|
createOrganizationMutation.mutate(data);
|
|
createOrganizationForm.reset();
|
|
})}
|
|
className="space-y-4"
|
|
>
|
|
<FormField
|
|
control={createOrganizationForm.control}
|
|
name="name"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Organization Name</FormLabel>
|
|
<FormControl>
|
|
<Input {...field} placeholder="Enter organization name" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={createOrganizationForm.control}
|
|
name="isActive"
|
|
render={({ field }) => (
|
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
|
<div className="space-y-0.5">
|
|
<FormLabel className="text-base">Active Status</FormLabel>
|
|
<FormDescription>
|
|
Enable this organization upon creation
|
|
</FormDescription>
|
|
</div>
|
|
<FormControl>
|
|
<Switch
|
|
checked={field.value}
|
|
onCheckedChange={field.onChange}
|
|
/>
|
|
</FormControl>
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<Button
|
|
type="submit"
|
|
disabled={createOrganizationMutation.isPending}
|
|
>
|
|
{createOrganizationMutation.isPending ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Creating...
|
|
</>
|
|
) : (
|
|
"Create Organization"
|
|
)}
|
|
</Button>
|
|
</form>
|
|
</Form>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Organization Details</CardTitle>
|
|
<CardDescription>
|
|
Manage your organization information.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Form {...organizationForm}>
|
|
<form onSubmit={organizationForm.handleSubmit(onOrganizationSubmit)} className="space-y-4">
|
|
<FormField
|
|
control={organizationForm.control}
|
|
name="name"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Organization Name</FormLabel>
|
|
<FormControl>
|
|
<Input {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={organizationForm.control}
|
|
name="isActive"
|
|
render={({ field }) => (
|
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
|
<div className="space-y-0.5">
|
|
<FormLabel className="text-base">Active Status</FormLabel>
|
|
<FormDescription>
|
|
Enable or disable this organization
|
|
</FormDescription>
|
|
</div>
|
|
<FormControl>
|
|
<Switch
|
|
checked={field.value}
|
|
onCheckedChange={field.onChange}
|
|
/>
|
|
</FormControl>
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<Button
|
|
type="submit"
|
|
disabled={updateOrganizationMutation.isPending}
|
|
>
|
|
{updateOrganizationMutation.isPending ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Saving...
|
|
</>
|
|
) : (
|
|
"Save Changes"
|
|
)}
|
|
</Button>
|
|
</form>
|
|
</Form>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{user?.role === "admin" && (
|
|
<Card className="border-destructive">
|
|
<CardHeader>
|
|
<CardTitle>Danger Zone</CardTitle>
|
|
<CardDescription>
|
|
Irreversible actions for your organization
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-4">
|
|
<div className="rounded-lg border border-destructive p-4">
|
|
<div className="flex flex-row items-center justify-between">
|
|
<div>
|
|
<h4 className="font-medium">Delete Organization</h4>
|
|
<p className="text-sm text-muted-foreground">
|
|
Permanently delete this organization and all associated data
|
|
</p>
|
|
</div>
|
|
<Button
|
|
variant="destructive"
|
|
onClick={() => setIsDeleteDialogOpen(true)}
|
|
>
|
|
Delete
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</>
|
|
)}
|
|
</TabsContent>
|
|
|
|
{/* Appearance Settings */}
|
|
<TabsContent value="appearance" className="space-y-6">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Appearance</CardTitle>
|
|
<CardDescription>
|
|
Customize the application appearance and theme.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
<div className="space-y-2">
|
|
<h3 className="text-lg font-medium">Theme</h3>
|
|
<p className="text-sm text-muted-foreground">
|
|
Select your preferred theme appearance
|
|
</p>
|
|
<div className="grid grid-cols-3 gap-4 pt-2">
|
|
<div
|
|
className={`flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground cursor-pointer ${
|
|
theme === "light" ? "border-primary" : ""
|
|
}`}
|
|
onClick={() => setTheme("light")}
|
|
>
|
|
<Sun className="h-6 w-6 mb-3" />
|
|
<div className="space-y-1 text-center">
|
|
<h3 className="font-medium">Light</h3>
|
|
<p className="text-xs text-muted-foreground">Light background with dark text</p>
|
|
</div>
|
|
</div>
|
|
<div
|
|
className={`flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground cursor-pointer ${
|
|
theme === "dark" ? "border-primary" : ""
|
|
}`}
|
|
onClick={() => setTheme("dark")}
|
|
>
|
|
<Moon className="h-6 w-6 mb-3" />
|
|
<div className="space-y-1 text-center">
|
|
<h3 className="font-medium">Dark</h3>
|
|
<p className="text-xs text-muted-foreground">Dark background with light text</p>
|
|
</div>
|
|
</div>
|
|
<div
|
|
className={`flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground cursor-pointer ${
|
|
theme === "system" ? "border-primary" : ""
|
|
}`}
|
|
onClick={() => setTheme("system")}
|
|
>
|
|
<Laptop className="h-6 w-6 mb-3" />
|
|
<div className="space-y-1 text-center">
|
|
<h3 className="font-medium">System</h3>
|
|
<p className="text-xs text-muted-foreground">Follow your system preference</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
</Tabs>
|
|
|
|
{/* Delete Organization Dialog */}
|
|
<AlertDialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
|
|
<AlertDialogContent>
|
|
<AlertDialogHeader>
|
|
<AlertDialogTitle>Delete Organization</AlertDialogTitle>
|
|
<AlertDialogDescription>
|
|
Are you sure you want to delete the organization <strong>{currentOrganization?.name}</strong>?
|
|
This action cannot be undone. All domains, DNS records, and related data will be permanently deleted.
|
|
</AlertDialogDescription>
|
|
</AlertDialogHeader>
|
|
<AlertDialogFooter>
|
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
<AlertDialogAction
|
|
onClick={() => deleteOrganizationMutation.mutate()}
|
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
disabled={deleteOrganizationMutation.isPending}
|
|
>
|
|
{deleteOrganizationMutation.isPending ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Deleting...
|
|
</>
|
|
) : (
|
|
"Delete Organization"
|
|
)}
|
|
</AlertDialogAction>
|
|
</AlertDialogFooter>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
</MainLayout>
|
|
);
|
|
}
|