- Clone import: defer FurnitureData.json writes and append all entries in a single batched write instead of one read-modify-write per item, removing the main serialization bottleneck for large batches. - Clone import: raise SSE batch concurrency cap from 5 to 10 and bump the clone client/route default from 2 to 6. - Add a flush hook to runSseBatch so callers can batch deferred work before batch_complete is emitted, and surface flush errors as an error event. - Enable Next.js Cache Components (instant: false opt-out) and silence the related build warnings in next.config.ts. - Switch isomorphic-dompurify to dompurify and refresh dependencies.
147 lines
4.4 KiB
TypeScript
147 lines
4.4 KiB
TypeScript
import { sql } from "drizzle-orm";
|
|
import { redirect } from "next/navigation";
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import { db } from "@/lib/db";
|
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
|
import { getWatchedUserIds } from "@/lib/services/watch";
|
|
import { UserAccountInfo } from "../../_components/user-account-info";
|
|
import { UserHero } from "../../_components/user-hero";
|
|
import { loadUserById } from "../../_lib/load-user";
|
|
import { UserActivityLog } from "../../[id]/user-activity-log";
|
|
import { UserBadgesSection } from "../../[id]/user-badges-section";
|
|
import { UserBanSection } from "../../[id]/user-ban-section";
|
|
import { UserEditForm } from "../../[id]/user-edit-form";
|
|
import { UserQuickActions } from "../../[id]/user-quick-actions";
|
|
import { UserRooms } from "../../[id]/user-rooms";
|
|
|
|
// TODO: Cache Components adoption. Refactor this route so this opt-out can be removed.
|
|
// See: https://nextjs.org/docs/app/guides/migrating-to-cache-components
|
|
export const instant = false;
|
|
|
|
export default async function UserEditPage({
|
|
params,
|
|
}: {
|
|
params: Promise<{ locale: string; id: string }>;
|
|
}) {
|
|
const { id } = await params;
|
|
const { session, permissions } = await getAdminContext();
|
|
if (!canAccess(permissions, PERMS.USERS_EDIT, session.user.rank)) {
|
|
redirect("/admin");
|
|
}
|
|
|
|
const data = await loadUserById(id);
|
|
const watched = await getWatchedUserIds(session.user.id);
|
|
|
|
// Fetch rank list from permissions table for the rank dropdown
|
|
const [rawRanks] = (await db.execute(
|
|
sql`SELECT id, rank_name FROM permissions ORDER BY id ASC`,
|
|
)) as unknown as [{ id: number; rank_name: string }[], unknown];
|
|
const ranks = (rawRanks ?? []).map((r) => ({
|
|
id: Number(r.id),
|
|
name: String(r.rank_name || `Rank ${r.id}`),
|
|
}));
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<UserHero
|
|
user={data.user}
|
|
diamonds={data.diamonds}
|
|
duckets={data.duckets}
|
|
roomCount={data.roomCount}
|
|
chatCount={data.chatCount}
|
|
tradeCount={data.tradeCount}
|
|
isOnline={data.isOnline}
|
|
activeBan={!!data.activeBan}
|
|
mode="edit"
|
|
canEdit={true}
|
|
watching={watched.has(data.user.id)}
|
|
/>
|
|
|
|
<div className="grid gap-6 lg:grid-cols-[1fr_320px] xl:grid-cols-[1fr_360px]">
|
|
<Tabs defaultValue="edit" className="space-y-4 min-w-0">
|
|
<TabsList className="flex-wrap">
|
|
<TabsTrigger value="edit">Edit Profile</TabsTrigger>
|
|
<TabsTrigger value="bans">
|
|
Bans
|
|
{data.activeBan && (
|
|
<span className="ml-1.5 flex h-2 w-2 rounded-full bg-destructive" />
|
|
)}
|
|
</TabsTrigger>
|
|
<TabsTrigger value="badges">
|
|
Badges ({data.user.badges.length})
|
|
</TabsTrigger>
|
|
<TabsTrigger value="rooms">Rooms ({data.roomCount})</TabsTrigger>
|
|
<TabsTrigger value="activity">Activity</TabsTrigger>
|
|
<TabsTrigger value="info">Account Info</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="edit">
|
|
<UserEditForm
|
|
user={{
|
|
id: data.user.id,
|
|
username: data.user.username,
|
|
mail: data.user.mail,
|
|
rank: data.user.rank,
|
|
motto: data.user.motto,
|
|
credits: data.user.credits,
|
|
pixels: data.user.pixels,
|
|
diamonds: data.diamonds,
|
|
duckets: data.duckets,
|
|
}}
|
|
ranks={ranks}
|
|
/>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="bans">
|
|
<UserBanSection
|
|
userId={data.user.id}
|
|
username={data.user.username}
|
|
bans={data.user.bansReceived.map((b) => ({
|
|
id: b.id,
|
|
reason: b.banReason,
|
|
type: b.type,
|
|
staffName: b.staff.username,
|
|
timestamp: b.timestamp,
|
|
banExpire: b.banExpire,
|
|
}))}
|
|
/>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="badges">
|
|
<UserBadgesSection
|
|
userId={data.user.id}
|
|
badges={data.user.badges.map((b) => ({
|
|
id: b.id,
|
|
badgeCode: b.badgeCode,
|
|
}))}
|
|
/>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="rooms">
|
|
<UserRooms rooms={data.userRooms} />
|
|
</TabsContent>
|
|
|
|
<TabsContent value="activity">
|
|
<UserActivityLog
|
|
chatLogs={data.chatLogs}
|
|
commandLogs={data.commandLogs}
|
|
/>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="info">
|
|
<UserAccountInfo user={data.user} />
|
|
</TabsContent>
|
|
</Tabs>
|
|
|
|
<aside className="lg:sticky lg:top-4 lg:self-start">
|
|
<UserQuickActions
|
|
userId={data.user.id}
|
|
username={data.user.username}
|
|
isOnline={data.isOnline}
|
|
/>
|
|
</aside>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|