diff --git a/app/admin/users/create/page.tsx b/app/admin/users/create/page.tsx index a099bea..f8773fb 100644 --- a/app/admin/users/create/page.tsx +++ b/app/admin/users/create/page.tsx @@ -12,12 +12,14 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { Alert, AlertDescription } from "@/components/ui/alert" import { UserPlus, Copy, CheckCircle } from "lucide-react" import { createUserManually } from "../user-management-actions" +import { useToast } from "@/hooks/use-toast" export default function CreateUserPage() { const router = useRouter() const [loading, setLoading] = useState(false) const [error, setError] = useState("") const [success, setSuccess] = useState<{ email: string; password: string } | null>(null) + const { toast } = useToast() const [formData, setFormData] = useState({ email: "", @@ -32,14 +34,35 @@ export default function CreateUserPage() { setLoading(true) setError("") + if (!formData.role) { + const message = "Please select a role for this user." + setError(message) + setLoading(false) + toast({ + title: "Role required", + description: message, + variant: "destructive", + }) + return + } + const result = await createUserManually(formData) if (result.error) { setError(result.error) setLoading(false) + toast({ + title: "Failed to create user", + description: result.error, + variant: "destructive", + }) } else if (result.success && result.tempPassword) { setSuccess({ email: result.email, password: result.tempPassword }) setLoading(false) + toast({ + title: "User created", + description: "Temporary credentials have been generated.", + }) } } diff --git a/app/admin/users/existing-users-table.tsx b/app/admin/users/existing-users-table.tsx index 7cec0f4..3a37f57 100644 --- a/app/admin/users/existing-users-table.tsx +++ b/app/admin/users/existing-users-table.tsx @@ -15,6 +15,8 @@ import { } from "@/components/ui/dialog" import { Label } from "@/components/ui/label" import { Textarea } from "@/components/ui/textarea" +import { Alert, AlertDescription } from "@/components/ui/alert" +import { useToast } from "@/hooks/use-toast" import { Edit, Ban, CheckCircle, Trash2 } from "lucide-react" import { updateUserRole, disableUser, enableUser, deleteUser } from "./user-management-actions" @@ -39,40 +41,155 @@ export function ExistingUsersTable({ users }: { users: User[] }) { const [deleteDialog, setDeleteDialog] = useState<{ open: boolean; userId?: string; userName?: string }>({ open: false, }) - const [loading, setLoading] = useState(false) + const [loadingUserId, setLoadingUserId] = useState(null) + const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null) + const { toast } = useToast() const handleUpdateRole = async () => { if (!roleDialog.userId) return - setLoading(true) - await updateUserRole(roleDialog.userId, selectedRole) - setLoading(false) + // Prevent demoting the last active admin + const targetUser = users.find((u) => u.id === roleDialog.userId) + const activeAdminCount = users.filter((u) => u.role === "admin" && u.is_active).length + const isDemotingLastAdmin = + targetUser && targetUser.role === "admin" && targetUser.is_active && selectedRole !== "admin" && activeAdminCount === 1 + + if (isDemotingLastAdmin) { + const message = "You cannot remove the last active admin. Create another admin first." + setMessage({ type: "error", text: message }) + toast({ + title: "Cannot change role", + description: message, + variant: "destructive", + }) + return + } + + setLoadingUserId(roleDialog.userId) + setMessage(null) + const result = await updateUserRole(roleDialog.userId, selectedRole) + setLoadingUserId(null) setRoleDialog({ open: false }) + if (result?.error) { + setMessage({ type: "error", text: result.error }) + toast({ + title: "Failed to update role", + description: result.error, + variant: "destructive", + }) + } else { + const msg = "User role updated successfully." + setMessage({ type: "success", text: msg }) + toast({ + title: "Role updated", + description: msg, + }) + } } const handleDisable = async () => { if (!disableDialog.userId) return - setLoading(true) - await disableUser(disableDialog.userId, disableReason) - setLoading(false) + const targetUser = users.find((u) => u.id === disableDialog.userId) + const activeAdminCount = users.filter((u) => u.role === "admin" && u.is_active).length + const isDisablingLastAdmin = + targetUser && targetUser.role === "admin" && targetUser.is_active && activeAdminCount === 1 + + if (isDisablingLastAdmin) { + const message = "You cannot disable the last active admin. Create another admin first." + setMessage({ type: "error", text: message }) + toast({ + title: "Cannot disable user", + description: message, + variant: "destructive", + }) + return + } + + setLoadingUserId(disableDialog.userId) + setMessage(null) + const result = await disableUser(disableDialog.userId, disableReason) + setLoadingUserId(null) setDisableDialog({ open: false }) setDisableReason("") + if (result?.error) { + setMessage({ type: "error", text: result.error }) + toast({ + title: "Failed to disable user", + description: result.error, + variant: "destructive", + }) + } else { + const msg = "User disabled successfully." + setMessage({ type: "success", text: msg }) + toast({ + title: "User disabled", + description: msg, + }) + } } const handleEnable = async (userId: string) => { - setLoading(true) - await enableUser(userId) - setLoading(false) + setLoadingUserId(userId) + setMessage(null) + const result = await enableUser(userId) + setLoadingUserId(null) + if (result?.error) { + setMessage({ type: "error", text: result.error }) + toast({ + title: "Failed to enable user", + description: result.error, + variant: "destructive", + }) + } else { + const msg = "User enabled successfully." + setMessage({ type: "success", text: msg }) + toast({ + title: "User enabled", + description: msg, + }) + } } const handleDelete = async () => { if (!deleteDialog.userId) return - setLoading(true) - await deleteUser(deleteDialog.userId) - setLoading(false) + const targetUser = users.find((u) => u.id === deleteDialog.userId) + const activeAdminCount = users.filter((u) => u.role === "admin" && u.is_active).length + const isDeletingLastAdmin = + targetUser && targetUser.role === "admin" && targetUser.is_active && activeAdminCount === 1 + + if (isDeletingLastAdmin) { + const message = "You cannot delete the last active admin. Create another admin first." + setMessage({ type: "error", text: message }) + toast({ + title: "Cannot delete user", + description: message, + variant: "destructive", + }) + return + } + + setLoadingUserId(deleteDialog.userId) + setMessage(null) + const result = await deleteUser(deleteDialog.userId) + setLoadingUserId(null) setDeleteDialog({ open: false }) + if (result?.error) { + setMessage({ type: "error", text: result.error }) + toast({ + title: "Failed to delete user", + description: result.error, + variant: "destructive", + }) + } else { + const msg = "User deleted successfully." + setMessage({ type: "success", text: msg }) + toast({ + title: "User deleted", + description: msg, + }) + } } const roleColors: Record = { @@ -86,6 +203,13 @@ export function ExistingUsersTable({ users }: { users: User[] }) { return ( <> + {message && ( +
+ + {message.text} + +
+ )} @@ -137,12 +261,18 @@ export function ExistingUsersTable({ users }: { users: User[] }) { size="sm" variant="outline" onClick={() => setDisableDialog({ open: true, userId: user.id })} + disabled={loadingUserId === user.id} > Disable ) : ( - @@ -157,7 +287,7 @@ export function ExistingUsersTable({ users }: { users: User[] }) { userName: `${user.first_name || ""} ${user.last_name || ""}`.trim() || user.email, }) } - > + > Delete @@ -195,7 +325,10 @@ export function ExistingUsersTable({ users }: { users: User[] }) { - @@ -223,7 +356,11 @@ export function ExistingUsersTable({ users }: { users: User[] }) { - @@ -243,7 +380,11 @@ export function ExistingUsersTable({ users }: { users: User[] }) { - diff --git a/app/admin/users/pending-registrations-table.tsx b/app/admin/users/pending-registrations-table.tsx index 82fb87b..ce68e38 100644 --- a/app/admin/users/pending-registrations-table.tsx +++ b/app/admin/users/pending-registrations-table.tsx @@ -18,6 +18,7 @@ import { Textarea } from "@/components/ui/textarea" import { CheckCircle, XCircle, Copy } from "lucide-react" import { approveRegistration, rejectRegistration } from "./user-management-actions" import { Alert, AlertDescription } from "@/components/ui/alert" +import { useToast } from "@/hooks/use-toast" interface Registration { id: string @@ -37,17 +38,42 @@ export function PendingRegistrationsTable({ registrations }: { registrations: Re const [rejectDialog, setRejectDialog] = useState<{ open: boolean; registrationId?: string }>({ open: false }) const [rejectionReason, setRejectionReason] = useState("") const [loading, setLoading] = useState(null) + const [error, setError] = useState(null) + const { toast } = useToast() const handleApprove = async (registrationId: string) => { + setError(null) const assignedRole = selectedRoles[registrationId] || registrations.find((r) => r.id === registrationId)?.requested_role + if (!assignedRole) { + const message = "Please select a role before approving this registration." + setError(message) + toast({ + title: "Role required", + description: message, + variant: "destructive", + }) + return + } + setLoading(registrationId) - const result = await approveRegistration(registrationId, assignedRole!) + const result = await approveRegistration(registrationId, assignedRole) setLoading(null) if (result.success && result.tempPassword) { setApprovalDialog({ open: true, email: result.email, password: result.tempPassword }) + toast({ + title: "User approved", + description: "The user account has been created successfully.", + }) + } else if (result.error) { + setError(result.error) + toast({ + title: "Failed to approve user", + description: result.error, + variant: "destructive", + }) } } @@ -55,10 +81,23 @@ export function PendingRegistrationsTable({ registrations }: { registrations: Re if (!rejectDialog.registrationId) return setLoading(rejectDialog.registrationId) - await rejectRegistration(rejectDialog.registrationId, rejectionReason) + const result = await rejectRegistration(rejectDialog.registrationId, rejectionReason) setLoading(null) setRejectDialog({ open: false }) setRejectionReason("") + + if (result?.error) { + toast({ + title: "Failed to reject registration", + description: result.error, + variant: "destructive", + }) + } else { + toast({ + title: "Registration rejected", + description: "The registration request has been rejected.", + }) + } } const copyCredentials = () => { @@ -77,6 +116,13 @@ export function PendingRegistrationsTable({ registrations }: { registrations: Re return ( <> + {error && ( +
+ + {error} + +
+ )}
diff --git a/app/admin/users/pending-team-members-table.tsx b/app/admin/users/pending-team-members-table.tsx index cbf8e27..db99ce7 100644 --- a/app/admin/users/pending-team-members-table.tsx +++ b/app/admin/users/pending-team-members-table.tsx @@ -16,6 +16,7 @@ import { import { CheckCircle, Copy } from "lucide-react" import { approveTeamMember } from "./user-management-actions" import { Alert, AlertDescription } from "@/components/ui/alert" +import { useToast } from "@/hooks/use-toast" interface TeamMember { id: string @@ -33,18 +34,41 @@ export function PendingTeamMembersTable({ teamMembers }: { teamMembers: TeamMemb open: false, }) const [loading, setLoading] = useState(null) + const [error, setError] = useState(null) + const { toast } = useToast() const handleApprove = async (teamMemberId: string) => { + setError(null) const assignedRole = selectedRoles[teamMemberId] || teamMembers.find((tm) => tm.id === teamMemberId)?.role + if (!assignedRole) { + const message = "Please select a role before approving this team member." + setError(message) + toast({ + title: "Role required", + description: message, + variant: "destructive", + }) + return + } + setLoading(teamMemberId) - const result = await approveTeamMember(teamMemberId, assignedRole!) + const result = await approveTeamMember(teamMemberId, assignedRole) setLoading(null) if (result.success && result.tempPassword) { setApprovalDialog({ open: true, email: result.email, password: result.tempPassword }) + toast({ + title: "Team member approved", + description: "The user account has been created successfully.", + }) } else if (result.error) { - alert(result.error) + setError(result.error) + toast({ + title: "Failed to approve team member", + description: result.error, + variant: "destructive", + }) } } @@ -64,6 +88,13 @@ export function PendingTeamMembersTable({ teamMembers }: { teamMembers: TeamMemb return ( <> + {error && ( +
+ + {error} + +
+ )}