Files
buzz/app/(ui)/admin/tl/[ver]/page.tsx
T
dmgnr 8c14999c59
Build and Push Image / build-backend (push) Successful in 6m13s
Build and Push Image / build-frontend (push) Successful in 7m33s
Build and Push Image / push (push) Successful in 1m16s
feat(admin): tierlist editor
2026-08-06 03:00:13 +07:00

138 lines
5.2 KiB
TypeScript
Executable File

import { desc, eq } from "drizzle-orm";
import { ExternalLink, Lock, PencilLine, PlusIcon } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { SimpleTooltip } from "@/components/tooltip";
import { Button } from "@/components/ui/button";
import { Kbd } from "@/components/ui/kbd";
import { db } from "@/lib/db";
import {
tierlistBadges,
tierlistTypes,
tierlistVersions,
} from "@/lib/db/schema";
import { syncTierlistSnapshots } from "@/lib/tierlist";
import { cn } from "@/lib/utils";
import { BadgeSection } from "./badge-section";
export default async function TierlistManagerPage({
params,
}: PageProps<"/admin/tl/[ver]">) {
const { ver } = await params;
await syncTierlistSnapshots(ver);
const [[type], versionsList, badges] = await Promise.all([
db.select().from(tierlistTypes).where(eq(tierlistTypes.id, ver)),
db
.select()
.from(tierlistVersions)
.orderBy(desc(tierlistVersions.order))
.where(eq(tierlistVersions.type, ver)),
db
.select()
.from(tierlistBadges)
.orderBy(tierlistBadges.order, tierlistBadges.id),
]);
const t = {
...type,
versions: versionsList,
};
return (
<div className="mx-2 flex grow-0 flex-col gap-2 pt-5">
<div className="group/type flex flex-col gap-1">
<div className="flex w-fit flex-wrap items-center gap-2 text-4xl font-semibold">
<div className="flex flex-auto flex-wrap items-baseline gap-x-2 rounded-md border bg-[#2228] px-2 py-1">
<span className="text-nowrap">{t.name}</span>
<span className="flex items-center gap-1">
<Kbd>{t.id}</Kbd>
<span className="text-xs text-muted-foreground">{t.mode}</span>
</span>
</div>
<div className="flex gap-1 transition-opacity group-hover/type:opacity-100 md:opacity-0">
<SimpleTooltip text="Add version">
<Button asChild>
<Link href={`/admin/tl/ver/${t.id}/create`}>
<PlusIcon />
<span className="md:hidden">Add</span>
</Link>
</Button>
</SimpleTooltip>
<SimpleTooltip text="Edit type">
<Button variant="outline" asChild>
<Link href={`/admin/tl/ver/${t.id}/edit`}>
<PencilLine />
<span className="md:hidden">Edit</span>
</Link>
</Button>
</SimpleTooltip>
</div>
</div>
<div className="flex flex-wrap gap-2">
{t.versions.map((e) => (
<div
key={e.id}
className={cn(
e.image
? "group/ver relative aspect-video w-[calc(100svw-16px)] rounded-sm border md:w-60"
: "group/ver relative flex aspect-video w-[calc(100svw-16px)] items-center justify-center rounded-sm border bg-[#1118] text-4xl font-bold backdrop-blur-xl md:w-60",
e.hidden && "opacity-50",
)}
>
{e.image ? (
<>
<Image
src={`/cdn/${e.image}`}
alt={e.name}
fill
className="-z-1 rounded-sm border bg-primary object-cover"
/>
<div className="absolute bottom-0 flex w-full justify-center gap-1 rounded-b-sm bg-card py-1">
{e.name} <Kbd>{e.id}</Kbd>
</div>
</>
) : (
e.name
)}
<div className="absolute top-1 right-1 flex gap-1 transition-opacity group-hover/ver:opacity-100 md:opacity-0">
<SimpleTooltip text="Open in admin">
<Button variant="outline" asChild className="bg-card!">
<Link href={`/tl/${t.id}/${e.id}/admin`} target="_blank">
<ExternalLink />
<span className="md:hidden">Open</span>
</Link>
</Button>
</SimpleTooltip>
<SimpleTooltip text="Edit version">
<Button variant="outline" asChild className="bg-card!">
<Link href={`/admin/tl/ver/${t.id}/${e.id}/edit`}>
<PencilLine />
<span className="md:hidden">Edit</span>
</Link>
</Button>
</SimpleTooltip>
</div>
{e.snapshot && (
<div className="absolute right-1 bottom-1 rounded-sm border bg-card/90 p-1 text-muted-foreground">
<SimpleTooltip text="This version is using a frozen snapshot">
<Lock className="size-4" />
</SimpleTooltip>
</div>
)}
</div>
))}
</div>
</div>
<div className="h-px bg-foreground/25"></div>
<div className="flex">
<div className="flex flex-1 flex-col gap-2">
<BadgeSection
typeId={ver}
typeName={type.name}
badges={badges.filter((badge) => (badge.type ?? ver) === ver)}
/>
</div>
<div className="flex flex-2"></div>
</div>
</div>
);
}