|
|
|
|
|
"use client"; |
|
|
|
|
|
import { |
|
|
Dialog, |
|
|
DialogContent, |
|
|
DialogHeader, |
|
|
DialogTitle, |
|
|
DialogDescription, |
|
|
DialogFooter, |
|
|
DialogClose, |
|
|
} from "@/components/ui/dialog"; |
|
|
import { Button } from "@/components/ui/button"; |
|
|
import { Input } from "@/components/ui/input"; |
|
|
import { Label } from "@/components/ui/label"; |
|
|
import { |
|
|
Select, |
|
|
SelectContent, |
|
|
SelectItem, |
|
|
SelectTrigger, |
|
|
SelectValue, |
|
|
} from "@/components/ui/select"; |
|
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; |
|
|
import { useForm } from "react-hook-form"; |
|
|
import { zodResolver } from "@hookform/resolvers/zod"; |
|
|
import { z } from "zod"; |
|
|
import { UpdateUserRoleAdminSchema, UpdateUserCoinsAdminSchema, type UserRole } from "@/lib/schemas"; |
|
|
import { updateUserRoleAdmin, updateUserCoinsAdmin } from "@/lib/actions/admin"; |
|
|
import { useToast } from "@/hooks/use-toast"; |
|
|
import { useState, useEffect } from "react"; |
|
|
import { Loader2 } from "lucide-react"; |
|
|
import type { User } from "@/lib/types"; |
|
|
|
|
|
interface EditUserDialogProps { |
|
|
user: User | null; |
|
|
isOpen: boolean; |
|
|
onOpenChange: (isOpen: boolean) => void; |
|
|
onSuccess: () => void; |
|
|
} |
|
|
|
|
|
type EditUserFormData = { |
|
|
newRole: UserRole; |
|
|
coinAdjustment: number; |
|
|
}; |
|
|
|
|
|
export function EditUserDialog({ user, isOpen, onOpenChange, onSuccess }: EditUserDialogProps) { |
|
|
const { toast } = useToast(); |
|
|
const [isSubmittingRole, setIsSubmittingRole] = useState(false); |
|
|
const [isSubmittingCoins, setIsSubmittingCoins] = useState(false); |
|
|
|
|
|
const form = useForm<EditUserFormData>({ |
|
|
resolver: zodResolver( |
|
|
z.object({ |
|
|
newRole: UpdateUserRoleAdminSchema.shape.newRole, |
|
|
coinAdjustment: UpdateUserCoinsAdminSchema.shape.coinAdjustment, |
|
|
}) |
|
|
), |
|
|
defaultValues: { |
|
|
newRole: user?.role || "user", |
|
|
coinAdjustment: 0, |
|
|
}, |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
if (user) { |
|
|
form.reset({ |
|
|
newRole: user.role || "user", |
|
|
coinAdjustment: 0, |
|
|
}); |
|
|
} |
|
|
}, [user, form.reset]); |
|
|
|
|
|
|
|
|
if (!user) return null; |
|
|
|
|
|
const handleRoleSubmit = async (values: { newRole: UserRole }) => { |
|
|
setIsSubmittingRole(true); |
|
|
try { |
|
|
const result = await updateUserRoleAdmin({ userId: user._id, newRole: values.newRole }); |
|
|
toast({ |
|
|
title: result.success ? "Success" : "Error", |
|
|
description: result.message, |
|
|
variant: result.success ? "default" : "destructive", |
|
|
}); |
|
|
if (result.success) { |
|
|
onSuccess(); |
|
|
} |
|
|
} catch (error) { |
|
|
toast({ title: "Error", description: "An unexpected error occurred.", variant: "destructive" }); |
|
|
} finally { |
|
|
setIsSubmittingRole(false); |
|
|
} |
|
|
}; |
|
|
|
|
|
const handleCoinsSubmit = async (values: { coinAdjustment: number }) => { |
|
|
setIsSubmittingCoins(true); |
|
|
try { |
|
|
const result = await updateUserCoinsAdmin({ userId: user._id, coinAdjustment: values.coinAdjustment }); |
|
|
toast({ |
|
|
title: result.success ? "Success" : "Error", |
|
|
description: result.message, |
|
|
variant: result.success ? "default" : "destructive", |
|
|
}); |
|
|
if (result.success) { |
|
|
form.setValue("coinAdjustment", 0); |
|
|
onSuccess(); |
|
|
} |
|
|
} catch (error) { |
|
|
toast({ title: "Error", description: "An unexpected error occurred.", variant: "destructive" }); |
|
|
} finally { |
|
|
setIsSubmittingCoins(false); |
|
|
} |
|
|
}; |
|
|
|
|
|
return ( |
|
|
<Dialog open={isOpen} onOpenChange={onOpenChange}> |
|
|
<DialogContent className="sm:max-w-[480px]"> |
|
|
<DialogHeader> |
|
|
<DialogTitle>Edit User: {user.name}</DialogTitle> |
|
|
<DialogDescription> |
|
|
Modify user role and coin balance. Current email: {user.email} (cannot be changed). |
|
|
</DialogDescription> |
|
|
</DialogHeader> |
|
|
|
|
|
<Form {...form}> |
|
|
<div className="space-y-6 py-4"> |
|
|
{/* Role Management Form */} |
|
|
<form onSubmit={form.handleSubmit(data => handleRoleSubmit({ newRole: data.newRole }))} className="space-y-4 p-4 border rounded-md"> |
|
|
<FormField |
|
|
control={form.control} |
|
|
name="newRole" |
|
|
render={({ field }) => ( |
|
|
<FormItem> |
|
|
<FormLabel>User Role</FormLabel> |
|
|
<Select onValueChange={field.onChange} defaultValue={field.value}> |
|
|
<FormControl> |
|
|
<SelectTrigger> |
|
|
<SelectValue placeholder="Select a role" /> |
|
|
</SelectTrigger> |
|
|
</FormControl> |
|
|
<SelectContent> |
|
|
<SelectItem value="user">User</SelectItem> |
|
|
<SelectItem value="admin">Admin</SelectItem> |
|
|
</SelectContent> |
|
|
</Select> |
|
|
<FormMessage /> |
|
|
</FormItem> |
|
|
)} |
|
|
/> |
|
|
<Button type="submit" disabled={isSubmittingRole || user.role === form.getValues("newRole")}> |
|
|
{isSubmittingRole && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} |
|
|
Update Role |
|
|
</Button> |
|
|
</form> |
|
|
|
|
|
{/* Coin Management Form */} |
|
|
<form onSubmit={form.handleSubmit(data => handleCoinsSubmit({ coinAdjustment: data.coinAdjustment }))} className="space-y-4 p-4 border rounded-md"> |
|
|
<FormField |
|
|
control={form.control} |
|
|
name="coinAdjustment" |
|
|
render={({ field }) => ( |
|
|
<FormItem> |
|
|
<FormLabel>Adjust Coins (Current: {user.coins.toLocaleString()})</FormLabel> |
|
|
<FormControl> |
|
|
<Input |
|
|
type="number" |
|
|
placeholder="e.g., 50 or -20" |
|
|
{...field} |
|
|
onChange={e => field.onChange(parseInt(e.target.value,10) || 0)} |
|
|
/> |
|
|
</FormControl> |
|
|
<FormMessage /> |
|
|
</FormItem> |
|
|
)} |
|
|
/> |
|
|
<Button type="submit" disabled={isSubmittingCoins || form.getValues("coinAdjustment") === 0}> |
|
|
{isSubmittingCoins && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} |
|
|
Adjust Coins |
|
|
</Button> |
|
|
</form> |
|
|
</div> |
|
|
</Form> |
|
|
|
|
|
<DialogFooter> |
|
|
<DialogClose asChild> |
|
|
<Button type="button" variant="outline"> |
|
|
Close |
|
|
</Button> |
|
|
</DialogClose> |
|
|
</DialogFooter> |
|
|
</DialogContent> |
|
|
</Dialog> |
|
|
); |
|
|
} |
|
|
|