Reapply "Add missing admin action files and navigation links"
This reverts commit 4d515bc400.
This commit is contained in:
1 parent
96ed768f14
commit
5b4228261a
338 files changed
+28143
-5948
No files matched your search
@@ -0,0 +1,204 @@
|
||||
'use client'
|
||||
|
||||
import { Loader2, UserPlus } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { createUser } from '@/actions/users'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@/components/ui/dialog'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { useServerAction } from '@/hooks/use-server-action'
|
||||
|
||||
interface CreateUserDialogProps {
|
||||
sessionRank: number
|
||||
}
|
||||
|
||||
const RANK_OPTIONS: { value: number; label: string }[] = [
|
||||
{ value: 1, label: 'User (1)' },
|
||||
{ value: 2, label: 'Bronze (2)' },
|
||||
{ value: 3, label: 'Silver (3)' },
|
||||
{ value: 4, label: 'Gold (4)' },
|
||||
{ value: 5, label: 'Moderator (5)' },
|
||||
{ value: 6, label: 'Admin (6)' },
|
||||
{ value: 7, label: 'Super Admin (7)' },
|
||||
]
|
||||
|
||||
export function CreateUserDialog({ sessionRank }: CreateUserDialogProps) {
|
||||
const { run, isPending } = useServerAction()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [username, setUsername] = useState('')
|
||||
const [mail, setMail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [rank, setRank] = useState('1')
|
||||
const [motto, setMotto] = useState('')
|
||||
|
||||
const allowedRanks = RANK_OPTIONS.filter((r) => (sessionRank >= 7 ? true : r.value < sessionRank))
|
||||
|
||||
function reset() {
|
||||
setUsername('')
|
||||
setMail('')
|
||||
setPassword('')
|
||||
setRank('1')
|
||||
setMotto('')
|
||||
}
|
||||
|
||||
function generatePassword() {
|
||||
const charset = 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789'
|
||||
let out = ''
|
||||
const buf = new Uint32Array(12)
|
||||
crypto.getRandomValues(buf)
|
||||
for (let i = 0; i < buf.length; i++) out += charset[buf[i] % charset.length]
|
||||
setPassword(out)
|
||||
toast.message('Password generated', { description: out })
|
||||
}
|
||||
|
||||
function handleCreate() {
|
||||
const u = username.trim()
|
||||
const m = mail.trim()
|
||||
if (!u || !m || !password) {
|
||||
toast.error('Username, email and password are required')
|
||||
return
|
||||
}
|
||||
if (u.length < 3 || u.length > 20 || !/^[a-zA-Z0-9_-]+$/.test(u)) {
|
||||
toast.error('Invalid username (3-20 chars, letters/digits/_/-)')
|
||||
return
|
||||
}
|
||||
if (password.length < 6) {
|
||||
toast.error('Password must be at least 6 characters')
|
||||
return
|
||||
}
|
||||
|
||||
run(
|
||||
() =>
|
||||
createUser({
|
||||
username: u,
|
||||
mail: m,
|
||||
password,
|
||||
rank: Number(rank),
|
||||
motto: motto.trim(),
|
||||
}),
|
||||
{
|
||||
successMessage: `User "${u}" created.`,
|
||||
onSuccess: () => {
|
||||
setOpen(false)
|
||||
reset()
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(next) => {
|
||||
setOpen(next)
|
||||
if (!next) reset()
|
||||
}}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<Button>
|
||||
<UserPlus className="mr-2 h-4 w-4" />
|
||||
New User
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create User</DialogTitle>
|
||||
<DialogDescription>
|
||||
Account is created immediately with default look and 5,000 credits/duckets/diamonds.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cu-username">Username</Label>
|
||||
<Input
|
||||
id="cu-username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
placeholder="HabboName"
|
||||
autoComplete="off"
|
||||
maxLength={20}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cu-mail">Email</Label>
|
||||
<Input
|
||||
id="cu-mail"
|
||||
type="email"
|
||||
value={mail}
|
||||
onChange={(e) => setMail(e.target.value)}
|
||||
placeholder="[email protected]"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cu-password">Password</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="cu-password"
|
||||
type="text"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Minimum 6 characters"
|
||||
autoComplete="new-password"
|
||||
className="font-mono"
|
||||
/>
|
||||
<Button type="button" variant="outline" onClick={generatePassword}>
|
||||
Generate
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Rank</Label>
|
||||
<Select value={rank} onValueChange={setRank}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{allowedRanks.map((r) => (
|
||||
<SelectItem key={r.value} value={String(r.value)}>
|
||||
{r.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cu-motto">Motto (optional)</Label>
|
||||
<Input
|
||||
id="cu-motto"
|
||||
value={motto}
|
||||
onChange={(e) => setMotto(e.target.value)}
|
||||
maxLength={127}
|
||||
placeholder="I'm new here!"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button onClick={handleCreate} disabled={isPending} className="w-full">
|
||||
{isPending ? (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<UserPlus className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
Create User
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user