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