feat(hk): expand search and add operational user overview
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { Loader2, UserPlus } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { createUser } from "@/actions/users";
|
||||
@@ -28,17 +29,10 @@ 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)" },
|
||||
];
|
||||
const RANK_OPTIONS = [1, 2, 3, 4, 5, 6, 7];
|
||||
|
||||
export function CreateUserDialog({ sessionRank }: CreateUserDialogProps) {
|
||||
const t = useTranslations("pages.admin.users.create");
|
||||
const { run, isPending } = useServerAction();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [username, setUsername] = useState("");
|
||||
@@ -48,7 +42,7 @@ export function CreateUserDialog({ sessionRank }: CreateUserDialogProps) {
|
||||
const [motto, setMotto] = useState("");
|
||||
|
||||
const allowedRanks = RANK_OPTIONS.filter((r) =>
|
||||
sessionRank >= 7 ? true : r.value < sessionRank,
|
||||
sessionRank >= 7 ? true : r < sessionRank,
|
||||
);
|
||||
|
||||
function reset() {
|
||||
@@ -67,22 +61,22 @@ export function CreateUserDialog({ sessionRank }: CreateUserDialogProps) {
|
||||
for (let i = 0; i < buf.length; i++)
|
||||
out += charset[buf[i] % charset.length];
|
||||
setPassword(out);
|
||||
toast.message("Password generated", { description: out });
|
||||
toast.message(t("passwordGenerated"));
|
||||
}
|
||||
|
||||
function handleCreate() {
|
||||
const u = username.trim();
|
||||
const m = mail.trim();
|
||||
if (!u || !m || !password) {
|
||||
toast.error("Username, email and password are required");
|
||||
toast.error(t("required"));
|
||||
return;
|
||||
}
|
||||
if (u.length < 3 || u.length > 20 || !/^[a-zA-Z0-9_-]+$/.test(u)) {
|
||||
toast.error("Invalid username (3-20 chars, letters/digits/_/-)");
|
||||
toast.error(t("invalidUsername"));
|
||||
return;
|
||||
}
|
||||
if (password.length < 6) {
|
||||
toast.error("Password must be at least 6 characters");
|
||||
toast.error(t("passwordLength"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -96,7 +90,7 @@ export function CreateUserDialog({ sessionRank }: CreateUserDialogProps) {
|
||||
motto: motto.trim(),
|
||||
}),
|
||||
{
|
||||
successMessage: `User "${u}" created.`,
|
||||
successMessage: t("created", { username: u }),
|
||||
onSuccess: () => {
|
||||
setOpen(false);
|
||||
reset();
|
||||
@@ -116,31 +110,28 @@ export function CreateUserDialog({ sessionRank }: CreateUserDialogProps) {
|
||||
<DialogTrigger asChild>
|
||||
<Button>
|
||||
<UserPlus className="mr-2 h-4 w-4" />
|
||||
New User
|
||||
{t("newUser")}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create User</DialogTitle>
|
||||
<DialogDescription>
|
||||
Account is created immediately with default look and 5,000
|
||||
credits/duckets/diamonds.
|
||||
</DialogDescription>
|
||||
<DialogTitle>{t("title")}</DialogTitle>
|
||||
<DialogDescription>{t("description")}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cu-username">Username</Label>
|
||||
<Label htmlFor="cu-username">{t("username")}</Label>
|
||||
<Input
|
||||
id="cu-username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
placeholder="HabboName"
|
||||
placeholder={t("usernamePlaceholder")}
|
||||
autoComplete="off"
|
||||
maxLength={20}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cu-mail">Email</Label>
|
||||
<Label htmlFor="cu-mail">{t("email")}</Label>
|
||||
<Input
|
||||
id="cu-mail"
|
||||
type="email"
|
||||
@@ -151,14 +142,14 @@ export function CreateUserDialog({ sessionRank }: CreateUserDialogProps) {
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cu-password">Password</Label>
|
||||
<Label htmlFor="cu-password">{t("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"
|
||||
placeholder={t("passwordPlaceholder")}
|
||||
autoComplete="new-password"
|
||||
className="font-mono"
|
||||
/>
|
||||
@@ -167,33 +158,33 @@ export function CreateUserDialog({ sessionRank }: CreateUserDialogProps) {
|
||||
variant="outline"
|
||||
onClick={generatePassword}
|
||||
>
|
||||
Generate
|
||||
{t("generate")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Rank</Label>
|
||||
<Label>{t("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 key={r} value={String(r)}>
|
||||
{t("rankOption", { rank: r })}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cu-motto">Motto (optional)</Label>
|
||||
<Label htmlFor="cu-motto">{t("motto")}</Label>
|
||||
<Input
|
||||
id="cu-motto"
|
||||
value={motto}
|
||||
onChange={(e) => setMotto(e.target.value)}
|
||||
maxLength={127}
|
||||
placeholder="I'm new here!"
|
||||
placeholder={t("mottoPlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -207,7 +198,7 @@ export function CreateUserDialog({ sessionRank }: CreateUserDialogProps) {
|
||||
) : (
|
||||
<UserPlus className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
Create User
|
||||
{t("title")}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
Reference in New Issue
Block a user