207 lines
7.0 KiB
TypeScript
207 lines
7.0 KiB
TypeScript
"use client";
|
|
|
|
import { Check, Coins, Copy, KeyRound, MessageCircle, Unplug, Volume2, VolumeX } from "lucide-react";
|
|
import { useState } from "react";
|
|
import { alertUser, disconnectUser, muteUser, resetPassword, sendCredits, unmuteUser } from "@/actions/users";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { useServerAction } from "@/hooks/use-server-action";
|
|
|
|
interface UserQuickActionsProps {
|
|
userId: number;
|
|
username: string;
|
|
isOnline: boolean;
|
|
}
|
|
|
|
export function UserQuickActions({ userId, username, isOnline }: UserQuickActionsProps) {
|
|
const { run, isPending } = useServerAction();
|
|
|
|
// Reset password state
|
|
const [newPassword, setNewPassword] = useState<string | null>(null);
|
|
const [copied, setCopied] = useState(false);
|
|
|
|
// Alert state
|
|
const [alertMessage, setAlertMessage] = useState("");
|
|
|
|
// Mute state
|
|
const [muteDuration, setMuteDuration] = useState("10");
|
|
|
|
// Credits state
|
|
const [creditAmount, setCreditAmount] = useState("1000");
|
|
|
|
function handleResetPassword() {
|
|
if (!confirm(`Reset password for ${username}? A new random password will be generated.`)) return;
|
|
run(() => resetPassword({ userId }), {
|
|
successMessage: "Password reset successfully.",
|
|
onSuccess: (data) => {
|
|
setNewPassword(data.newPassword as string);
|
|
},
|
|
});
|
|
}
|
|
|
|
function handleDisconnect() {
|
|
run(() => disconnectUser({ userId }), {
|
|
successMessage: `${username} disconnected.`,
|
|
});
|
|
}
|
|
|
|
function handleAlert() {
|
|
if (!alertMessage.trim()) return;
|
|
run(() => alertUser({ userId, message: alertMessage.trim() }), {
|
|
successMessage: "Alert sent.",
|
|
onSuccess: () => setAlertMessage(""),
|
|
});
|
|
}
|
|
|
|
function handleMute() {
|
|
const duration = Number(muteDuration);
|
|
if (Number.isNaN(duration) || duration < 0) return;
|
|
run(() => muteUser({ userId, duration }), {
|
|
successMessage: `${username} muted.`,
|
|
});
|
|
}
|
|
|
|
function handleUnmute() {
|
|
run(() => unmuteUser({ userId }), {
|
|
successMessage: `${username} unmuted.`,
|
|
});
|
|
}
|
|
|
|
function handleSendCredits() {
|
|
const amount = parseInt(creditAmount, 10);
|
|
if (!amount || amount <= 0) return;
|
|
run(() => sendCredits({ userId, amount }), {
|
|
successMessage: `${amount.toLocaleString()} credits sent to ${username}.`,
|
|
});
|
|
}
|
|
|
|
function copyPassword() {
|
|
if (newPassword) {
|
|
navigator.clipboard.writeText(newPassword);
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 2000);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Quick Actions</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{/* Row 1: Password + Disconnect */}
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<Button
|
|
variant="outline"
|
|
onClick={handleResetPassword}
|
|
disabled={isPending}
|
|
className="justify-start"
|
|
>
|
|
<KeyRound className="mr-2 h-4 w-4" />
|
|
Reset Password
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
onClick={handleDisconnect}
|
|
disabled={isPending || !isOnline}
|
|
className="justify-start"
|
|
>
|
|
<Unplug className="mr-2 h-4 w-4" />
|
|
Disconnect
|
|
{isOnline && (
|
|
<Badge variant="default" className="ml-auto text-[10px] bg-[var(--admin-success-subtle)]">
|
|
Online
|
|
</Badge>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Send Alert */}
|
|
<div className="space-y-2">
|
|
<Label className="text-xs text-muted-foreground">Send In-Game Alert</Label>
|
|
<div className="flex gap-2">
|
|
<Input
|
|
placeholder="Message to send..."
|
|
value={alertMessage}
|
|
onChange={(e) => setAlertMessage(e.target.value)}
|
|
onKeyDown={(e) => e.key === "Enter" && handleAlert()}
|
|
className="flex-1"
|
|
/>
|
|
<Button onClick={handleAlert} disabled={isPending || !alertMessage.trim()} size="icon">
|
|
<MessageCircle className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Mute */}
|
|
<div className="space-y-2">
|
|
<Label className="text-xs text-muted-foreground">Mute / Unmute</Label>
|
|
<div className="flex gap-2">
|
|
<Input
|
|
type="number"
|
|
placeholder="Minutes (0 = permanent)"
|
|
value={muteDuration}
|
|
onChange={(e) => setMuteDuration(e.target.value)}
|
|
className="w-32"
|
|
/>
|
|
<Button variant="outline" onClick={handleMute} disabled={isPending} size="sm">
|
|
<VolumeX className="mr-1.5 h-3.5 w-3.5" /> Mute
|
|
</Button>
|
|
<Button variant="outline" onClick={handleUnmute} disabled={isPending} size="sm">
|
|
<Volume2 className="mr-1.5 h-3.5 w-3.5" /> Unmute
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Send Credits */}
|
|
<div className="space-y-2">
|
|
<Label className="text-xs text-muted-foreground">Send Credits (RCON)</Label>
|
|
<div className="flex gap-2">
|
|
<Input
|
|
type="number"
|
|
value={creditAmount}
|
|
onChange={(e) => setCreditAmount(e.target.value)}
|
|
className="w-32"
|
|
min={1}
|
|
/>
|
|
<Button variant="outline" onClick={handleSendCredits} disabled={isPending} size="sm">
|
|
<Coins className="mr-1.5 h-3.5 w-3.5" /> Send Credits
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Password Reset Dialog */}
|
|
<Dialog open={!!newPassword} onOpenChange={() => setNewPassword(null)}>
|
|
<DialogContent className="max-w-sm">
|
|
<DialogHeader>
|
|
<DialogTitle>Password Reset</DialogTitle>
|
|
<DialogDescription>
|
|
New password generated for <strong>{username}</strong>. Copy it now — it won't be shown
|
|
again.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="flex items-center gap-2 rounded-lg border bg-muted p-3">
|
|
<code className="flex-1 text-lg font-mono font-bold tracking-wider select-all">
|
|
{newPassword}
|
|
</code>
|
|
<Button variant="ghost" size="icon" onClick={copyPassword}>
|
|
{copied ? (
|
|
<Check className="h-4 w-4 text-[var(--admin-success)]" />
|
|
) : (
|
|
<Copy className="h-4 w-4" />
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</>
|
|
);
|
|
}
|