Change alert dialog to dialog
This commit is contained in:
parent
9380c371f8
commit
99692c37da
|
@ -1,29 +1,26 @@
|
||||||
import { useState } from "react"
|
import { useState, useEffect } from "react"
|
||||||
import { Loader2, ShieldAlert } from 'lucide-react'
|
import { Loader2, ShieldAlert } from 'lucide-react'
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
Dialog,
|
||||||
AlertDialogAction,
|
DialogContent,
|
||||||
AlertDialogCancel,
|
DialogDescription,
|
||||||
AlertDialogContent,
|
DialogFooter,
|
||||||
AlertDialogDescription,
|
DialogHeader,
|
||||||
AlertDialogFooter,
|
DialogTitle,
|
||||||
AlertDialogHeader,
|
} from "@/app/_components/ui/dialog"
|
||||||
AlertDialogTitle,
|
|
||||||
} from "@/app/_components/ui/alert-dialog"
|
|
||||||
import { Button } from "@/app/_components/ui/button"
|
import { Button } from "@/app/_components/ui/button"
|
||||||
|
import { RadioGroup, RadioGroupItem } from "@/app/_components/ui/radio-group"
|
||||||
import { Label } from "@/app/_components/ui/label"
|
import { Label } from "@/app/_components/ui/label"
|
||||||
import { Input } from "@/app/_components/ui/input"
|
import { Input } from "@/app/_components/ui/input"
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/app/_components/ui/select"
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/app/_components/ui/select"
|
||||||
import { RadioGroup, RadioGroupItem } from "@/app/_components/ui/radio-group"
|
|
||||||
import { ValidBanDuration } from "@/app/_lib/types/ban-duration"
|
import { ValidBanDuration } from "@/app/_lib/types/ban-duration"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
|
||||||
interface BanUserDialogProps {
|
interface BanUserDialogProps {
|
||||||
open: boolean
|
open: boolean
|
||||||
onOpenChange: (open: boolean) => void
|
onOpenChange: (open: boolean) => void
|
||||||
onConfirm: (duration: ValidBanDuration) => void
|
onConfirm: (duration: ValidBanDuration) => void
|
||||||
isPending?: boolean
|
isPending?: boolean
|
||||||
userId: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type BanDurationType = "preset" | "custom"
|
type BanDurationType = "preset" | "custom"
|
||||||
|
@ -33,35 +30,44 @@ export function BanUserDialog({
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
isPending = false,
|
isPending = false,
|
||||||
userId,
|
|
||||||
}: BanUserDialogProps) {
|
}: BanUserDialogProps) {
|
||||||
const [durationType, setDurationType] = useState<BanDurationType>("preset")
|
const [durationType, setDurationType] = useState<BanDurationType>("preset")
|
||||||
const [presetDuration, setPresetDuration] = useState("24h")
|
const [presetDuration, setPresetDuration] = useState("24h")
|
||||||
const [customValue, setCustomValue] = useState("1")
|
const [customValue, setCustomValue] = useState("1")
|
||||||
const [customUnit, setCustomUnit] = useState("days")
|
const [customUnit, setCustomUnit] = useState("days")
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) {
|
||||||
|
// Reset form when dialog closes
|
||||||
|
setDurationType("preset")
|
||||||
|
setPresetDuration("24h")
|
||||||
|
setCustomValue("1")
|
||||||
|
setCustomUnit("days")
|
||||||
|
}
|
||||||
|
}, [open])
|
||||||
|
|
||||||
const handleConfirm = () => {
|
const handleConfirm = () => {
|
||||||
let duration = ""
|
let duration = "24h"
|
||||||
|
|
||||||
if (durationType === "preset") {
|
if (durationType === "preset") {
|
||||||
duration = presetDuration
|
duration = presetDuration
|
||||||
} else {
|
} else {
|
||||||
// Convert to hours for consistency
|
const value = parseInt(customValue)
|
||||||
|
if (isNaN(value) || value < 1) return toast.error("Invalid duration")
|
||||||
|
|
||||||
switch (customUnit) {
|
switch (customUnit) {
|
||||||
case "hours":
|
case "hours":
|
||||||
duration = `${customValue}h`
|
duration = `${value}h`
|
||||||
break
|
break
|
||||||
case "days":
|
case "days":
|
||||||
duration = `${parseInt(customValue) * 24}h`
|
duration = `${value * 24}h`
|
||||||
break
|
break
|
||||||
case "weeks":
|
case "weeks":
|
||||||
duration = `${parseInt(customValue) * 24 * 7}h`
|
duration = `${value * 24 * 7}h`
|
||||||
break
|
break
|
||||||
case "months":
|
case "months":
|
||||||
duration = `${parseInt(customValue) * 24 * 30}h` // Approximation
|
duration = `${value * 24 * 30}h`
|
||||||
break
|
break
|
||||||
default:
|
|
||||||
duration = `${customValue}h`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,29 +75,29 @@ export function BanUserDialog({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertDialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<AlertDialogContent>
|
<DialogContent className="sm:max-w-md border-0">
|
||||||
<AlertDialogHeader>
|
<DialogHeader>
|
||||||
<AlertDialogTitle>Ban User</AlertDialogTitle>
|
<DialogTitle>Ban User</DialogTitle>
|
||||||
<AlertDialogDescription>
|
<DialogDescription>
|
||||||
This will prevent the user from accessing the system until the ban expires.
|
This will prevent the user from accessing the system until the ban expires.
|
||||||
</AlertDialogDescription>
|
</DialogDescription>
|
||||||
</AlertDialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="py-4">
|
<div className="py-4 space-y-4">
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
value={durationType}
|
value={durationType}
|
||||||
onValueChange={(value) => setDurationType(value as BanDurationType)}
|
onValueChange={(v) => setDurationType(v as BanDurationType)}
|
||||||
className="space-y-4"
|
className="space-y-4"
|
||||||
>
|
>
|
||||||
<div className="flex items-start space-x-2">
|
<div className="flex items-start space-x-2">
|
||||||
<RadioGroupItem value="preset" id="preset" />
|
<RadioGroupItem value="preset" id="preset" disabled={isPending} />
|
||||||
<div className="grid gap-2.5 w-full">
|
<div className="grid gap-2.5 w-full">
|
||||||
<Label htmlFor="preset" className="font-medium">Use preset duration</Label>
|
<Label htmlFor="preset">Use preset duration</Label>
|
||||||
<Select
|
<Select
|
||||||
value={presetDuration}
|
value={presetDuration}
|
||||||
onValueChange={setPresetDuration}
|
onValueChange={setPresetDuration}
|
||||||
disabled={durationType !== "preset"}
|
disabled={durationType !== "preset" || isPending}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-full">
|
<SelectTrigger className="w-full">
|
||||||
<SelectValue placeholder="Select duration" />
|
<SelectValue placeholder="Select duration" />
|
||||||
|
@ -110,22 +116,22 @@ export function BanUserDialog({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-start space-x-2">
|
<div className="flex items-start space-x-2">
|
||||||
<RadioGroupItem value="custom" id="custom" />
|
<RadioGroupItem value="custom" id="custom" disabled={isPending} />
|
||||||
<div className="grid gap-2.5 w-full">
|
<div className="grid gap-2.5 w-full">
|
||||||
<Label htmlFor="custom" className="font-medium">Custom duration</Label>
|
<Label htmlFor="custom">Custom duration</Label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
value={customValue}
|
value={customValue}
|
||||||
onChange={(e) => setCustomValue(e.target.value)}
|
onChange={(e) => setCustomValue(e.target.value)}
|
||||||
disabled={durationType !== "custom"}
|
disabled={durationType !== "custom" || isPending}
|
||||||
className="w-20"
|
className="w-20"
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
value={customUnit}
|
value={customUnit}
|
||||||
onValueChange={setCustomUnit}
|
onValueChange={setCustomUnit}
|
||||||
disabled={durationType !== "custom"}
|
disabled={durationType !== "custom" || isPending}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
|
@ -143,27 +149,35 @@ export function BanUserDialog({
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AlertDialogFooter>
|
<DialogFooter className="gap-2 sm:gap-0">
|
||||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
<Button
|
||||||
<AlertDialogAction
|
variant="outline"
|
||||||
onClick={handleConfirm}
|
onClick={() => onOpenChange(false)}
|
||||||
className="bg-yellow-500 text-white hover:bg-yellow-600"
|
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="bg-yellow-500 text-white hover:bg-yellow-600"
|
||||||
|
onClick={handleConfirm}
|
||||||
|
disabled={isPending}
|
||||||
|
type="submit"
|
||||||
>
|
>
|
||||||
{isPending ? (
|
{isPending ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
Banning...
|
Banning...
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<ShieldAlert className="h-4 w-4 mr-2" />
|
<ShieldAlert className="h-4 w-4" />
|
||||||
Ban User
|
Ban User
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</AlertDialogAction>
|
</Button>
|
||||||
</AlertDialogFooter>
|
</DialogFooter>
|
||||||
</AlertDialogContent>
|
</DialogContent>
|
||||||
</AlertDialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,17 +11,6 @@ import {
|
||||||
import { Button } from "@/app/_components/ui/button";
|
import { Button } from "@/app/_components/ui/button";
|
||||||
import { Badge } from "@/app/_components/ui/badge";
|
import { Badge } from "@/app/_components/ui/badge";
|
||||||
import { Separator } from "@/app/_components/ui/separator";
|
import { Separator } from "@/app/_components/ui/separator";
|
||||||
import {
|
|
||||||
AlertDialog,
|
|
||||||
AlertDialogAction,
|
|
||||||
AlertDialogCancel,
|
|
||||||
AlertDialogContent,
|
|
||||||
AlertDialogDescription,
|
|
||||||
AlertDialogFooter,
|
|
||||||
AlertDialogHeader,
|
|
||||||
AlertDialogTitle,
|
|
||||||
AlertDialogTrigger,
|
|
||||||
} from "@/app/_components/ui/alert-dialog";
|
|
||||||
import {
|
import {
|
||||||
Mail,
|
Mail,
|
||||||
Trash2,
|
Trash2,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import type { ColumnDef, HeaderContext } from "@tanstack/react-table"
|
import type { ColumnDef, HeaderContext } from "@tanstack/react-table"
|
||||||
import type { IUserSchema, IUserFilterOptionsSchema } from "@/src/entities/models/users/users.model"
|
import type { IUserSchema, IUserFilterOptionsSchema } from "@/src/entities/models/users/users.model"
|
||||||
import { ListFilter, MoreHorizontal, PenIcon as UserPen, Trash2, ShieldAlert, ShieldCheck } from "lucide-react"
|
import { ListFilter, MoreHorizontal, PenIcon as UserPen, Trash2, ShieldAlert, ShieldCheck } from "lucide-react"
|
||||||
|
@ -15,7 +16,7 @@ import { Input } from "@/app/_components/ui/input"
|
||||||
import { Avatar } from "@/app/_components/ui/avatar"
|
import { Avatar } from "@/app/_components/ui/avatar"
|
||||||
import Image from "next/image"
|
import Image from "next/image"
|
||||||
import { Badge } from "@/app/_components/ui/badge"
|
import { Badge } from "@/app/_components/ui/badge"
|
||||||
import { CAlertDialog } from "@/app/_components/alert-dialog"
|
import { ConfirmDialog } from "@/app/_components/confirm-dialog"
|
||||||
import { useCreateUserColumn } from "../_handlers/use-create-user-column"
|
import { useCreateUserColumn } from "../_handlers/use-create-user-column"
|
||||||
import { BanUserDialog } from "./ban-user-dialog"
|
import { BanUserDialog } from "./ban-user-dialog"
|
||||||
import { ValidBanDuration } from "@/app/_lib/types/ban-duration"
|
import { ValidBanDuration } from "@/app/_lib/types/ban-duration"
|
||||||
|
@ -31,22 +32,22 @@ export const createUserColumns = (
|
||||||
const {
|
const {
|
||||||
deleteDialogOpen,
|
deleteDialogOpen,
|
||||||
setDeleteDialogOpen,
|
setDeleteDialogOpen,
|
||||||
userToDelete,
|
|
||||||
setUserToDelete,
|
|
||||||
handleDeleteConfirm,
|
handleDeleteConfirm,
|
||||||
isDeletePending,
|
isDeletePending,
|
||||||
banDialogOpen,
|
banDialogOpen,
|
||||||
setBanDialogOpen,
|
setBanDialogOpen,
|
||||||
userToBan,
|
|
||||||
setUserToBan,
|
|
||||||
handleBanConfirm,
|
handleBanConfirm,
|
||||||
unbanDialogOpen,
|
unbanDialogOpen,
|
||||||
setUnbanDialogOpen,
|
setUnbanDialogOpen,
|
||||||
userToUnban,
|
|
||||||
setUserToUnban,
|
|
||||||
isBanPending,
|
isBanPending,
|
||||||
isUnbanPending,
|
isUnbanPending,
|
||||||
handleUnbanConfirm,
|
handleUnbanConfirm,
|
||||||
|
|
||||||
|
selectedUser,
|
||||||
|
setSelectedUser,
|
||||||
} = useCreateUserColumn()
|
} = useCreateUserColumn()
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
@ -339,7 +340,7 @@ export const createUserColumns = (
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setUserToDelete(row.original.id)
|
setSelectedUser({ id: row.original.id, email: row.original.email! })
|
||||||
setDeleteDialogOpen(true)
|
setDeleteDialogOpen(true)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -349,10 +350,10 @@ export const createUserColumns = (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (row.original.banned_until != null) {
|
if (row.original.banned_until != null) {
|
||||||
setUserToUnban(row.original.id)
|
setSelectedUser({ id: row.original.id, email: row.original.email! })
|
||||||
setUnbanDialogOpen(true)
|
setUnbanDialogOpen(true)
|
||||||
} else {
|
} else {
|
||||||
setUserToBan(row.original.id)
|
setSelectedUser({ id: row.original.id, email: row.original.email! })
|
||||||
setBanDialogOpen(true)
|
setBanDialogOpen(true)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@ -364,48 +365,42 @@ export const createUserColumns = (
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
||||||
{/* Alert Dialog for Delete Confirmation */}
|
{/* Alert Dialog for Delete Confirmation */}
|
||||||
{deleteDialogOpen && userToDelete === row.original.id && (
|
<ConfirmDialog
|
||||||
<CAlertDialog
|
|
||||||
open={deleteDialogOpen}
|
open={deleteDialogOpen}
|
||||||
onOpenChange={setDeleteDialogOpen}
|
onOpenChange={setDeleteDialogOpen}
|
||||||
title="Are you absolutely sure?"
|
title="Are you absolutely sure?"
|
||||||
description="This action cannot be undone. This will permanently delete the user account and remove their data from our servers."
|
description="This action cannot be undone. This will permanently delete the user account and remove their data from our servers."
|
||||||
confirmText="Delete"
|
confirmText="Delete"
|
||||||
onConfirm={handleDeleteConfirm}
|
onConfirm={() => handleDeleteConfirm(row.original.id, row.original.email!)}
|
||||||
isPending={isDeletePending}
|
isPending={isDeletePending}
|
||||||
pendingText="Deleting..."
|
pendingText="Deleting..."
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
size="sm"
|
size="sm"
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Alert Dialog for Ban Confirmation */}
|
{/* Alert Dialog for Ban Confirmation */}
|
||||||
{banDialogOpen && userToBan === row.original.id && (
|
|
||||||
<BanUserDialog
|
<BanUserDialog
|
||||||
open={banDialogOpen}
|
open={banDialogOpen}
|
||||||
onOpenChange={setBanDialogOpen}
|
onOpenChange={setBanDialogOpen}
|
||||||
onConfirm={(duration: ValidBanDuration) => handleBanConfirm(duration)}
|
onConfirm={handleBanConfirm}
|
||||||
isPending={isBanPending}
|
isPending={isBanPending}
|
||||||
userId={row.original.id}
|
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Alert Dialog for Unban Confirmation */}
|
{/* Alert Dialog for Unban Confirmation */}
|
||||||
{unbanDialogOpen && userToUnban === row.original.id && (
|
<ConfirmDialog
|
||||||
<CAlertDialog
|
|
||||||
open={unbanDialogOpen}
|
open={unbanDialogOpen}
|
||||||
onOpenChange={setUnbanDialogOpen}
|
onOpenChange={setUnbanDialogOpen}
|
||||||
title="Unban User"
|
title="Unban User"
|
||||||
description="This will restore the user's access to the system. Are you sure you want to unban this user?"
|
description="This will restore the user's access to the system. Are you sure you want to unban this user?"
|
||||||
confirmText="Unban"
|
confirmText="Unban"
|
||||||
onConfirm={handleUnbanConfirm}
|
onConfirm={() => handleUnbanConfirm(row.original.id, row.original.email!)}
|
||||||
isPending={isUnbanPending}
|
isPending={isUnbanPending}
|
||||||
pendingText="Unbanning..."
|
pendingText="Unbanning..."
|
||||||
variant="outline"
|
variant="default"
|
||||||
size="sm"
|
size="sm"
|
||||||
triggerIcon={<ShieldCheck className="h-4 w-4" />}
|
confirmIcon={<ShieldCheck className="h-4 w-4" />}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,6 +3,9 @@ import { useBanUserMutation, useDeleteUserMutation, useUnbanUserMutation } from
|
||||||
import { ValidBanDuration } from "@/app/_lib/types/ban-duration"
|
import { ValidBanDuration } from "@/app/_lib/types/ban-duration"
|
||||||
import { useQueryClient } from "@tanstack/react-query"
|
import { useQueryClient } from "@tanstack/react-query"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const useCreateUserColumn = () => {
|
export const useCreateUserColumn = () => {
|
||||||
|
|
||||||
|
@ -10,111 +13,99 @@ export const useCreateUserColumn = () => {
|
||||||
|
|
||||||
// Delete user state and handlers
|
// Delete user state and handlers
|
||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
|
||||||
const [userToDelete, setUserToDelete] = useState<string | null>(null)
|
|
||||||
const { mutateAsync: deleteUser, isPending: isDeletePending } = useDeleteUserMutation()
|
const { mutateAsync: deleteUser, isPending: isDeletePending } = useDeleteUserMutation()
|
||||||
|
|
||||||
// Ban user state and handlers
|
// Ban user state and handlers
|
||||||
const [banDialogOpen, setBanDialogOpen] = useState(false)
|
const [banDialogOpen, setBanDialogOpen] = useState(false)
|
||||||
const [userToBan, setUserToBan] = useState<string | null>(null)
|
|
||||||
const { mutateAsync: banUser, isPending: isBanPending } = useBanUserMutation()
|
const { mutateAsync: banUser, isPending: isBanPending } = useBanUserMutation()
|
||||||
|
|
||||||
// Unban user state and handlers
|
// Unban user state and handlers
|
||||||
const [unbanDialogOpen, setUnbanDialogOpen] = useState(false)
|
const [unbanDialogOpen, setUnbanDialogOpen] = useState(false)
|
||||||
const [userToUnban, setUserToUnban] = useState<string | null>(null)
|
|
||||||
const { mutateAsync: unbanUser, isPending: isUnbanPending } = useUnbanUserMutation()
|
const { mutateAsync: unbanUser, isPending: isUnbanPending } = useUnbanUserMutation()
|
||||||
|
|
||||||
const handleDeleteConfirm = async () => {
|
// Store selected user info
|
||||||
if (userToDelete) {
|
const [selectedUser, setSelectedUser] = useState<{ id: string, email: string } | null>(null)
|
||||||
await deleteUser(userToDelete, {
|
|
||||||
|
const handleDeleteConfirm = async (userId: string, email: string) => {
|
||||||
|
|
||||||
|
if (!userId) return toast.error("No user selected to delete")
|
||||||
|
|
||||||
|
await deleteUser(userId, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
if (isDeletePending) {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["users"] })
|
queryClient.invalidateQueries({ queryKey: ["users"] })
|
||||||
|
|
||||||
toast.success(`${userToDelete} has been deleted`)
|
toast.success(`${email} has been deleted`)
|
||||||
setDeleteDialogOpen(false)
|
setDeleteDialogOpen(false)
|
||||||
setUserToDelete(null)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
|
|
||||||
toast.error("Failed to delete user. Please try again later.")
|
toast.error("Failed to delete user. Please try again later.")
|
||||||
|
|
||||||
setDeleteDialogOpen(false)
|
setDeleteDialogOpen(false)
|
||||||
setUserToDelete(null)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleBanConfirm = async (duration: ValidBanDuration) => {
|
const handleBanConfirm = async (duration: ValidBanDuration) => {
|
||||||
if (userToBan) {
|
|
||||||
await banUser({ id: userToBan, ban_duration: duration }, {
|
if (!selectedUser) return toast.error("No user selected to ban")
|
||||||
|
|
||||||
|
await banUser({ id: selectedUser.id, ban_duration: duration }, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
if (!isBanPending) {
|
toast(`${selectedUser.email} has been banned`)
|
||||||
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["users"] })
|
queryClient.invalidateQueries({ queryKey: ["users"] })
|
||||||
|
|
||||||
toast(`${userToBan} has been banned`)
|
|
||||||
|
|
||||||
setBanDialogOpen(false)
|
setBanDialogOpen(false)
|
||||||
setUserToBan(null)
|
setSelectedUser(null)
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: () => {
|
||||||
toast.error("Failed to ban user. Please try again later.")
|
toast.error("Failed to ban user. Please try again later.")
|
||||||
|
|
||||||
setBanDialogOpen(false)
|
setBanDialogOpen(false)
|
||||||
setUserToBan(null)
|
setSelectedUser(null)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const handleUnbanConfirm = async () => {
|
const handleUnbanConfirm = async (userId: string, email: string) => {
|
||||||
if (userToUnban) {
|
|
||||||
await unbanUser({ id: userToUnban }, {
|
if (!userId) return toast.error("No user selected to unban")
|
||||||
|
|
||||||
|
await unbanUser({ id: userId }, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
if (!isUnbanPending) {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["users"] })
|
queryClient.invalidateQueries({ queryKey: ["users"] })
|
||||||
|
|
||||||
toast(`${userToUnban} has been unbanned`)
|
toast(`${email} has been unbanned`)
|
||||||
|
|
||||||
setUnbanDialogOpen(false)
|
setUnbanDialogOpen(false)
|
||||||
setUserToUnban(null)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error("Failed to unban user. Please try again later.")
|
toast.error("Failed to unban user. Please try again later.")
|
||||||
|
|
||||||
setUnbanDialogOpen(false)
|
setUnbanDialogOpen(false)
|
||||||
setUserToUnban(null)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Delete
|
// Delete
|
||||||
deleteDialogOpen,
|
deleteDialogOpen,
|
||||||
setDeleteDialogOpen,
|
setDeleteDialogOpen,
|
||||||
userToDelete,
|
|
||||||
setUserToDelete,
|
|
||||||
handleDeleteConfirm,
|
handleDeleteConfirm,
|
||||||
isDeletePending,
|
isDeletePending,
|
||||||
|
|
||||||
// Ban
|
// Ban
|
||||||
banDialogOpen,
|
banDialogOpen,
|
||||||
setBanDialogOpen,
|
setBanDialogOpen,
|
||||||
userToBan,
|
|
||||||
setUserToBan,
|
|
||||||
handleBanConfirm,
|
handleBanConfirm,
|
||||||
isBanPending,
|
isBanPending,
|
||||||
|
|
||||||
// Unban
|
// Unban
|
||||||
unbanDialogOpen,
|
unbanDialogOpen,
|
||||||
setUnbanDialogOpen,
|
setUnbanDialogOpen,
|
||||||
userToUnban,
|
|
||||||
setUserToUnban,
|
|
||||||
handleUnbanConfirm,
|
handleUnbanConfirm,
|
||||||
isUnbanPending,
|
isUnbanPending,
|
||||||
|
|
||||||
|
// Selected user
|
||||||
|
selectedUser,
|
||||||
|
setSelectedUser,
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
import type React from "react"
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react"
|
||||||
|
import { Loader2 } from "lucide-react"
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/app/_components/ui/dialog"
|
||||||
|
import { Button, type ButtonProps } from "@/app/_components/ui/button"
|
||||||
|
|
||||||
|
interface ConfirmDialogProps {
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
confirmText?: string
|
||||||
|
cancelText?: string
|
||||||
|
onConfirm: () => void
|
||||||
|
isPending?: boolean
|
||||||
|
pendingText?: string
|
||||||
|
variant?: ButtonProps["variant"]
|
||||||
|
size?: ButtonProps["size"]
|
||||||
|
open?: boolean
|
||||||
|
onOpenChange?: (open: boolean) => void
|
||||||
|
confirmIcon?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ConfirmDialog({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
confirmText = "Confirm",
|
||||||
|
cancelText = "Cancel",
|
||||||
|
onConfirm,
|
||||||
|
isPending = false,
|
||||||
|
pendingText = "Processing...",
|
||||||
|
variant = "default",
|
||||||
|
size = "default",
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
confirmIcon,
|
||||||
|
}: ConfirmDialogProps) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="sm:max-w-md border-0">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{title}</DialogTitle>
|
||||||
|
<DialogDescription>{description}</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter className="mt-4 gap-2 sm:gap-0">
|
||||||
|
<Button variant="outline" onClick={() => onOpenChange?.(false)} disabled={isPending}>
|
||||||
|
{cancelText}
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" variant={variant} onClick={onConfirm} disabled={isPending}>
|
||||||
|
{isPending ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
|
{pendingText}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{confirmIcon && <span className="">{confirmIcon}</span>}
|
||||||
|
{confirmText}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue