feat: add admin operations suite
Local Build and Deploy / deploy (push) Successful in 50s

This commit is contained in:
Simo committed 2026-07-12 20:11:28 +02:00
1 parent 21a61068fb
commit 60278b9e71
38 files changed
+3452

No files matched your search

@@ -0,0 +1,228 @@
'use client'
import {
DoorOpen,
Loader2,
Megaphone,
MessageSquare,
Shield,
Volume2,
VolumeX,
Zap,
} from 'lucide-react'
import { useState } from 'react'
import {
broadcastAlert,
quickAlert,
quickKick,
quickMute,
quickRoomKick,
quickUnmute,
} from '@/actions/moderation'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Textarea } from '@/components/ui/textarea'
import { useServerAction } from '@/hooks/use-server-action'
export function ModActionsClient() {
const { run, isPending } = useServerAction()
// User actions
const [userId, setUserId] = useState('')
const [muteDuration, setMuteDuration] = useState('10')
const [alertMessage, setAlertMessage] = useState('')
// Room actions
const [roomId, setRoomId] = useState('')
// Broadcast
const [broadcastMsg, setBroadcastMsg] = useState('')
const uid = parseInt(userId, 10)
const rid = parseInt(roomId, 10)
return (
<div className="grid gap-6 lg:grid-cols-2">
{/* User Actions */}
<Card>
<CardHeader>
<CardTitle className="text-sm flex items-center gap-2">
<Zap className="h-4 w-4 text-[var(--admin-warning)]" />
User Actions
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<span className="text-xs text-muted-foreground mb-1 block">User ID</span>
<Input
type="number"
value={userId}
onChange={(e) => setUserId(e.target.value)}
placeholder="Enter user ID"
/>
</div>
<div className="grid grid-cols-2 gap-2">
<Button
variant="destructive"
size="sm"
disabled={isPending || !uid}
onClick={() =>
run(() => quickKick({ userId: uid }), { successMessage: 'User disconnected' })
}
>
{isPending ? (
<Loader2 className="mr-1 h-3 w-3 animate-spin" />
) : (
<Zap className="mr-1 h-3 w-3" />
)}
Kick / Disconnect
</Button>
<Button
variant="outline"
size="sm"
disabled={isPending || !uid}
onClick={() =>
run(() => quickUnmute({ userId: uid }), { successMessage: 'User unmuted' })
}
>
<Volume2 className="mr-1 h-3 w-3" /> Unmute
</Button>
</div>
<div className="flex gap-2">
<Input
type="number"
min={1}
max={1440}
value={muteDuration}
onChange={(e) => setMuteDuration(e.target.value)}
className="w-24"
placeholder="Min"
/>
<Button
variant="outline"
size="sm"
className="flex-1"
disabled={isPending || !uid || !parseInt(muteDuration, 10)}
onClick={() =>
run(() => quickMute({ userId: uid, duration: parseInt(muteDuration, 10) }), {
successMessage: `Muted for ${muteDuration} min`,
})
}
>
<VolumeX className="mr-1 h-3 w-3" /> Mute
</Button>
</div>
<div className="flex gap-2">
<Textarea
value={alertMessage}
onChange={(e) => setAlertMessage(e.target.value)}
placeholder="Alert message to user..."
rows={2}
className="resize-none text-xs"
/>
<Button
variant="outline"
size="sm"
className="shrink-0 h-auto"
disabled={isPending || !uid || !alertMessage.trim()}
onClick={() => {
run(() => quickAlert({ userId: uid, message: alertMessage.trim() }), {
successMessage: 'Alert sent',
onSuccess: () => setAlertMessage(''),
})
}}
>
<MessageSquare className="h-4 w-4" />
</Button>
</div>
</CardContent>
</Card>
{/* Room Actions */}
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="text-sm flex items-center gap-2">
<DoorOpen className="h-4 w-4 text-[var(--admin-warning)]" />
Room Actions
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div>
<span className="text-xs text-muted-foreground mb-1 block">Room ID</span>
<Input
type="number"
value={roomId}
onChange={(e) => setRoomId(e.target.value)}
placeholder="Enter room ID"
/>
</div>
<Button
variant="destructive"
size="sm"
className="w-full"
disabled={isPending || !rid}
onClick={() =>
run(() => quickRoomKick({ roomId: rid }), {
successMessage: 'All users kicked from room',
})
}
>
<Zap className="mr-1 h-3 w-3" /> Kick All From Room
</Button>
</CardContent>
</Card>
{/* Broadcast */}
<Card>
<CardHeader>
<CardTitle className="text-sm flex items-center gap-2">
<Megaphone className="h-4 w-4 text-primary" />
Broadcast Alert
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<Textarea
value={broadcastMsg}
onChange={(e) => setBroadcastMsg(e.target.value)}
placeholder="Type broadcast message..."
rows={3}
className="resize-none"
/>
<div className="grid grid-cols-2 gap-2">
<Button
size="sm"
disabled={isPending || !broadcastMsg.trim()}
onClick={() => {
run(() => broadcastAlert({ message: broadcastMsg.trim(), type: 'hotel' }), {
successMessage: 'Hotel alert sent!',
onSuccess: () => setBroadcastMsg(''),
})
}}
>
<Megaphone className="mr-1 h-3 w-3" /> Hotel Alert
</Button>
<Button
variant="secondary"
size="sm"
disabled={isPending || !broadcastMsg.trim()}
onClick={() => {
run(() => broadcastAlert({ message: broadcastMsg.trim(), type: 'staff' }), {
successMessage: 'Staff alert sent!',
onSuccess: () => setBroadcastMsg(''),
})
}}
>
<Shield className="mr-1 h-3 w-3" /> Staff Alert
</Button>
</div>
</CardContent>
</Card>
</div>
</div>
)
}