revert fb8e77bb68
Local Build and Deploy / deploy (push) Successful in 1m11s
Local Build and Deploy / deploy (push) Successful in 1m11s
revert style: clean up code with prettier and eslint
This commit is contained in:
1 parent
27de078f54
commit
e85e4d74ea
378 files changed
+20710
-22428
No files matched your search
@@ -1,10 +1,10 @@
|
||||
"use client";
|
||||
'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 { 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,
|
||||
@@ -12,69 +12,75 @@ import {
|
||||
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";
|
||||
} 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;
|
||||
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)" },
|
||||
];
|
||||
{ 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 { 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));
|
||||
const allowedRanks = RANK_OPTIONS.filter((r) => (sessionRank >= 7 ? true : r.value < sessionRank))
|
||||
|
||||
function reset() {
|
||||
setUsername("");
|
||||
setMail("");
|
||||
setPassword("");
|
||||
setRank("1");
|
||||
setMotto("");
|
||||
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 });
|
||||
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();
|
||||
const u = username.trim()
|
||||
const m = mail.trim()
|
||||
if (!u || !m || !password) {
|
||||
toast.error("Username, email and password are required");
|
||||
return;
|
||||
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;
|
||||
toast.error('Invalid username (3-20 chars, letters/digits/_/-)')
|
||||
return
|
||||
}
|
||||
if (password.length < 6) {
|
||||
toast.error("Password must be at least 6 characters");
|
||||
return;
|
||||
toast.error('Password must be at least 6 characters')
|
||||
return
|
||||
}
|
||||
|
||||
run(
|
||||
@@ -89,19 +95,19 @@ export function CreateUserDialog({ sessionRank }: CreateUserDialogProps) {
|
||||
{
|
||||
successMessage: `User "${u}" created.`,
|
||||
onSuccess: () => {
|
||||
setOpen(false);
|
||||
reset();
|
||||
setOpen(false)
|
||||
reset()
|
||||
},
|
||||
},
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(next) => {
|
||||
setOpen(next);
|
||||
if (!next) reset();
|
||||
setOpen(next)
|
||||
if (!next) reset()
|
||||
}}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
@@ -194,5 +200,5 @@ export function CreateUserDialog({ sessionRank }: CreateUserDialogProps) {
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
)
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
import { Calendar, Clock, ExternalLink, Globe, MonitorSmartphone, Shield } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Calendar, Clock, ExternalLink, Globe, MonitorSmartphone, Shield } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
|
||||
interface UserAccountInfoProps {
|
||||
user: {
|
||||
accountCreated: number;
|
||||
lastLogin: number | null;
|
||||
ipRegister: string;
|
||||
ipCurrent: string;
|
||||
twoFactorConfirmed: number;
|
||||
machine_id: string;
|
||||
mail: string;
|
||||
gender: string;
|
||||
};
|
||||
accountCreated: number
|
||||
lastLogin: number | null
|
||||
ipRegister: string
|
||||
ipCurrent: string
|
||||
twoFactorConfirmed: number
|
||||
machine_id: string
|
||||
mail: string
|
||||
gender: string
|
||||
}
|
||||
}
|
||||
|
||||
export function UserAccountInfo({ user }: UserAccountInfoProps) {
|
||||
@@ -29,10 +29,10 @@ export function UserAccountInfo({ user }: UserAccountInfoProps) {
|
||||
<Calendar className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||
<span className="text-muted-foreground">Registered</span>
|
||||
<span className="ml-auto font-medium">
|
||||
{new Date(user.accountCreated * 1000).toLocaleDateString("it-IT", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
{new Date(user.accountCreated * 1000).toLocaleDateString('it-IT', {
|
||||
day: '2-digit',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
@@ -41,22 +41,27 @@ export function UserAccountInfo({ user }: UserAccountInfoProps) {
|
||||
<span className="text-muted-foreground">Last Login</span>
|
||||
<span className="ml-auto font-medium">
|
||||
{user.lastLogin
|
||||
? new Date(user.lastLogin * 1000).toLocaleDateString("it-IT", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
? new Date(user.lastLogin * 1000).toLocaleDateString('it-IT', {
|
||||
day: '2-digit',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
: "Never"}
|
||||
: 'Never'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<Globe className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||
<span className="text-muted-foreground">IP Register</span>
|
||||
<span className="ml-auto flex items-center gap-1.5">
|
||||
<code className="text-xs font-mono bg-muted px-2 py-0.5 rounded">{user.ipRegister}</code>
|
||||
<Link href={`/admin/users?search=${user.ipRegister}`} title="Find other users with this IP">
|
||||
<code className="text-xs font-mono bg-muted px-2 py-0.5 rounded">
|
||||
{user.ipRegister}
|
||||
</code>
|
||||
<Link
|
||||
href={`/admin/users?search=${user.ipRegister}`}
|
||||
title="Find other users with this IP"
|
||||
>
|
||||
<ExternalLink className="h-3 w-3 text-primary" />
|
||||
</Link>
|
||||
</span>
|
||||
@@ -65,8 +70,13 @@ export function UserAccountInfo({ user }: UserAccountInfoProps) {
|
||||
<Globe className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||
<span className="text-muted-foreground">IP Current</span>
|
||||
<span className="ml-auto flex items-center gap-1.5">
|
||||
<code className="text-xs font-mono bg-muted px-2 py-0.5 rounded">{user.ipCurrent}</code>
|
||||
<Link href={`/admin/users?search=${user.ipCurrent}`} title="Find other users with this IP">
|
||||
<code className="text-xs font-mono bg-muted px-2 py-0.5 rounded">
|
||||
{user.ipCurrent}
|
||||
</code>
|
||||
<Link
|
||||
href={`/admin/users?search=${user.ipCurrent}`}
|
||||
title="Find other users with this IP"
|
||||
>
|
||||
<ExternalLink className="h-3 w-3 text-primary" />
|
||||
</Link>
|
||||
</span>
|
||||
@@ -76,15 +86,15 @@ export function UserAccountInfo({ user }: UserAccountInfoProps) {
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<Shield className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||
<span className="text-muted-foreground">2FA</span>
|
||||
<Badge variant={user.twoFactorConfirmed ? "default" : "outline"} className="ml-auto">
|
||||
{user.twoFactorConfirmed ? "Enabled" : "Disabled"}
|
||||
<Badge variant={user.twoFactorConfirmed ? 'default' : 'outline'} className="ml-auto">
|
||||
{user.twoFactorConfirmed ? 'Enabled' : 'Disabled'}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<MonitorSmartphone className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||
<span className="text-muted-foreground">Machine ID</span>
|
||||
<code className="ml-auto text-xs font-mono bg-muted px-2 py-0.5 rounded truncate max-w-[180px]">
|
||||
{user.machine_id || "N/A"}
|
||||
{user.machine_id || 'N/A'}
|
||||
</code>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
@@ -101,5 +111,5 @@ export function UserAccountInfo({ user }: UserAccountInfoProps) {
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
)
|
||||
}
|
||||
@@ -1,19 +1,22 @@
|
||||
"use client";
|
||||
'use client'
|
||||
|
||||
interface Badge {
|
||||
id: number;
|
||||
badgeCode: string;
|
||||
id: number
|
||||
badgeCode: string
|
||||
}
|
||||
|
||||
export function UserBadgesReadonly({ badges }: { badges: Badge[] }) {
|
||||
if (badges.length === 0) {
|
||||
return <p className="text-sm text-muted-foreground">No badges</p>;
|
||||
return <p className="text-sm text-muted-foreground">No badges</p>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{badges.map((badge) => (
|
||||
<div key={badge.id} className="flex items-center gap-1.5 rounded-md border bg-card px-2 py-1 text-sm">
|
||||
<div
|
||||
key={badge.id}
|
||||
className="flex items-center gap-1.5 rounded-md border bg-card px-2 py-1 text-sm"
|
||||
>
|
||||
<img
|
||||
src={
|
||||
process.env.NEXT_PUBLIC_BADGE_URL
|
||||
@@ -23,12 +26,12 @@ export function UserBadgesReadonly({ badges }: { badges: Badge[] }) {
|
||||
alt={badge.badgeCode}
|
||||
className="h-5 w-5"
|
||||
onError={(e) => {
|
||||
(e.target as HTMLImageElement).style.display = "none";
|
||||
;(e.target as HTMLImageElement).style.display = 'none'
|
||||
}}
|
||||
/>
|
||||
<span>{badge.badgeCode}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
@@ -11,38 +11,38 @@ import {
|
||||
Sparkles,
|
||||
Wifi,
|
||||
WifiOff,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { AdminBreadcrumb } from "@/components/admin/admin-breadcrumb";
|
||||
import { UserWatchToggle } from "@/components/admin/user-watch-toggle";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { getAvatarUrl } from "@/lib/imager";
|
||||
} from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { AdminBreadcrumb } from '@/components/admin/admin-breadcrumb'
|
||||
import { UserWatchToggle } from '@/components/admin/user-watch-toggle'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { getAvatarUrl } from '@/lib/imager'
|
||||
|
||||
interface UserHeroProps {
|
||||
user: {
|
||||
id: number;
|
||||
username: string;
|
||||
motto: string;
|
||||
look: string;
|
||||
rank: number;
|
||||
credits: number;
|
||||
pixels: number;
|
||||
online: string;
|
||||
permission: { rankName: string };
|
||||
twoFactorConfirmed: number;
|
||||
};
|
||||
diamonds: number;
|
||||
duckets: number;
|
||||
roomCount: number;
|
||||
chatCount: number;
|
||||
tradeCount: number;
|
||||
isOnline: boolean;
|
||||
activeBan: boolean;
|
||||
mode: "show" | "edit";
|
||||
canEdit: boolean;
|
||||
id: number
|
||||
username: string
|
||||
motto: string
|
||||
look: string
|
||||
rank: number
|
||||
credits: number
|
||||
pixels: number
|
||||
online: string
|
||||
permission: { rankName: string }
|
||||
twoFactorConfirmed: number
|
||||
}
|
||||
diamonds: number
|
||||
duckets: number
|
||||
roomCount: number
|
||||
chatCount: number
|
||||
tradeCount: number
|
||||
isOnline: boolean
|
||||
activeBan: boolean
|
||||
mode: 'show' | 'edit'
|
||||
canEdit: boolean
|
||||
/** When non-null, render the watch toggle with this initial state. */
|
||||
watching?: boolean | null;
|
||||
watching?: boolean | null
|
||||
}
|
||||
|
||||
export function UserHero({
|
||||
@@ -62,9 +62,9 @@ export function UserHero({
|
||||
<>
|
||||
<AdminBreadcrumb
|
||||
items={[
|
||||
{ label: "Users", href: "/admin/users" },
|
||||
{ label: 'Users', href: '/admin/users' },
|
||||
{ label: user.username },
|
||||
{ label: mode === "edit" ? "Edit" : "View" },
|
||||
{ label: mode === 'edit' ? 'Edit' : 'View' },
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -76,14 +76,16 @@ export function UserHero({
|
||||
src={getAvatarUrl(user.look, { headDirection: 2, direction: 2 })}
|
||||
alt={user.username}
|
||||
className="h-28 rounded-lg border bg-card shadow-sm"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
style={{ imageRendering: 'pixelated' }}
|
||||
/>
|
||||
<span className={`absolute -top-1 -right-1 flex h-4 w-4 ${isOnline ? "" : "opacity-50"}`}>
|
||||
<span
|
||||
className={`absolute -top-1 -right-1 flex h-4 w-4 ${isOnline ? '' : 'opacity-50'}`}
|
||||
>
|
||||
{isOnline && (
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-[var(--admin-success)] opacity-75" />
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-[var(--admin-success)] opacity-75" />
|
||||
)}
|
||||
<span
|
||||
className={`relative inline-flex rounded-full h-4 w-4 border-2 border-card ${isOnline ? "bg-[var(--admin-success-subtle)]" : "bg-[var(--admin-text-muted)]"}`}
|
||||
className={`relative inline-flex rounded-full h-4 w-4 border-2 border-card ${isOnline ? 'bg-[var(--admin-success-subtle)]' : 'bg-[var(--admin-text-muted)]'}`}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
@@ -96,14 +98,14 @@ export function UserHero({
|
||||
ID: {user.id}
|
||||
</Badge>
|
||||
{/* Mode switch buttons */}
|
||||
{mode === "show" && canEdit && (
|
||||
{mode === 'show' && canEdit && (
|
||||
<Button variant="outline" size="sm" asChild className="ml-2">
|
||||
<Link href={`/admin/users/edit/${user.id}`}>
|
||||
<Pencil className="mr-1.5 h-3.5 w-3.5" /> Edit
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
{mode === "edit" && (
|
||||
{mode === 'edit' && (
|
||||
<Button variant="outline" size="sm" asChild className="ml-2">
|
||||
<Link href={`/admin/users/show/${user.id}`}>
|
||||
<Eye className="mr-1.5 h-3.5 w-3.5" /> View
|
||||
@@ -111,17 +113,23 @@ export function UserHero({
|
||||
</Button>
|
||||
)}
|
||||
{watching != null && (
|
||||
<UserWatchToggle targetUserId={user.id} username={user.username} initialWatching={watching} />
|
||||
<UserWatchToggle
|
||||
targetUserId={user.id}
|
||||
username={user.username}
|
||||
initialWatching={watching}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{user.motto && <p className="text-muted-foreground italic">“{user.motto}”</p>}
|
||||
{user.motto && (
|
||||
<p className="text-muted-foreground italic">“{user.motto}”</p>
|
||||
)}
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Badge>{user.permission.rankName || `Rank ${user.rank}`}</Badge>
|
||||
<Badge
|
||||
variant={isOnline ? "default" : "outline"}
|
||||
className={isOnline ? "bg-[var(--admin-success-subtle)]" : ""}
|
||||
variant={isOnline ? 'default' : 'outline'}
|
||||
className={isOnline ? 'bg-[var(--admin-success-subtle)]' : ''}
|
||||
>
|
||||
{isOnline ? (
|
||||
<>
|
||||
@@ -135,10 +143,7 @@ export function UserHero({
|
||||
</Badge>
|
||||
{activeBan && <Badge variant="destructive">Banned</Badge>}
|
||||
{user.twoFactorConfirmed ? (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="border-[var(--admin-success-border)] text-[var(--admin-success)]"
|
||||
>
|
||||
<Badge variant="outline" className="border-[var(--admin-success-border)] text-[var(--admin-success)]">
|
||||
<Shield className="mr-1 h-3 w-3" /> 2FA
|
||||
</Badge>
|
||||
) : null}
|
||||
@@ -193,5 +198,5 @@ export function UserHero({
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user