Files
buzz-sheet/app/admin/[character]/[page]/page.tsx
T

180 lines
6.6 KiB
TypeScript

import Link from "next/link";
import Image from "@/components/ui/resilient-image";
import { ArrowLeftIcon } from "lucide-react";
import { notFound, redirect } from "next/navigation";
import { connection } from "next/server";
import { GuideSectionEditor } from "@/components/admin/guide-section-editor";
import { GuideSectionNav } from "@/components/admin/guide-section-nav";
import { AdminHeader } from "@/components/admin/admin-header";
import { EditorPresence } from "@/components/admin/editor-presence";
import { Button } from "@/components/ui/button";
import { getAdminSession } from "@/lib/auth/server";
import { toTeamDraft } from "@/lib/guides/team-draft";
import {
getCatalogOptions,
getAdminGuideContent,
getGuideEditorHeader,
} from "@/lib/guides/queries";
const sections = [
"weapon",
"artifact",
"constellations",
"team",
] as const;
export const instant = false;
export default async function StructuredEditorPage({
params,
}: {
params: Promise<{ character: string; page: string }>;
}) {
await connection();
if (!(await getAdminSession())) redirect("/auth/login?next=%2Fadmin");
const { character, page } = await params;
const guide = await getGuideEditorHeader(character);
if (!guide) notFound();
const extraSections = guide.sections.filter((section) => section.kind === "extra");
if (page === "extra") {
redirect(
extraSections[0]
? `/admin/${character}/${extraSections[0].slug}`
: `/admin/${character}`,
);
}
const fixedSection = sections.includes(page as (typeof sections)[number]);
const extraSection = extraSections.find((section) => section.slug === page);
if (!fixedSection && !extraSection) notFound();
const editorSection = fixedSection
? page as (typeof sections)[number]
: "extra" as const;
const sectionTitle = extraSection?.title
?? guide.sections.find((section) => section.slug === page)?.title
?? page;
const [catalog, content] = await Promise.all([
getCatalogOptions(),
getAdminGuideContent(guide.slug, page),
]);
if (!catalog || !content) notFound();
const guideCharacter = content.character;
return (
<div className="min-h-svh">
<AdminHeader />
<main className="mx-auto flex w-full max-w-7xl flex-col gap-6 px-4 pb-4 sm:px-6 sm:pb-6 lg:px-8 lg:pb-8">
<header className="flex items-center gap-4">
<Button
variant="ghost"
size="icon"
render={<Link href="/admin" />}
nativeButton={false}
aria-label="กลับ Dashboard"
>
<ArrowLeftIcon />
</Button>
<div className="relative size-16 shrink-0 overflow-hidden rounded-lg border">
<Image src={guide.coverUrl} alt={guide.name} fill sizes="64px" className="object-cover" />
</div>
<div className="min-w-0">
<p className="text-sm text-muted-foreground">{guide.name}</p>
<h1 className="whitespace-nowrap font-heading text-3xl font-semibold">
{sectionTitle}
</h1>
<EditorPresence guideId={guide.id} section={sectionTitle} />
</div>
<Button className="ml-auto" variant="outline" render={<Link href={`/admin/${guide.characterKey}/preview/${page}`} target="_blank" />} nativeButton={false}>
Preview
</Button>
</header>
<GuideSectionNav
guideId={guide.id}
characterKey={guide.characterKey}
current={page}
extras={extraSections.map(({ id, slug, title }) => ({ id, slug, title }))}
/>
<GuideSectionEditor
key={`${editorSection}:${extraSection?.id ?? "fixed"}:${guide.version}`}
section={editorSection}
extraSectionId={extraSection?.id}
guide={guide}
weapons={catalog.weapons}
artifacts={catalog.artifacts}
characters={catalog.characters}
catalogConstellations={guideCharacter?.constellations ?? []}
catalogTalents={guideCharacter?.talents ?? []}
initial={{
sectionNote:
guide.sections.find((section) => section.slug === page)?.note ??
null,
weaponGroups: content.weaponGroups,
artifactGroups: content.artifactGroups.map((group) => ({
id: group.id,
name: group.name,
note: group.note,
iconMediaId: group.iconMediaId,
iconUrl: group.iconUrl,
tiers: group.tiers.map((tier) => ({
id: tier.id,
name: tier.name,
rows: tier.rows.map((row) => ({
firstArtifactKey: row.firstArtifactKey,
secondArtifactKey: row.secondArtifactKey,
percent: row.percent,
note: row.note,
})),
})),
})),
artifactProfile: content.artifactProfile,
constellationGroups: content.constellationGroups.map((group) => ({
id: group.id,
name: group.name,
note: group.note,
iconMediaId: group.iconMediaId,
iconUrl: group.iconUrl,
rows: group.rows,
})),
teams: content.teams.map((team) => ({
id: team.id,
...toTeamDraft(team),
})),
teamGroups: content.teamGroups.map((group) => ({
id: group.id,
name: group.name,
note: group.note,
iconMediaId: group.iconMediaId,
iconUrl: group.iconUrl,
teams: group.teams.map((team) => ({
id: team.id,
...toTeamDraft(team),
})),
})),
extras: content.extras.filter((section) => !extraSection || section.id === extraSection.id).map((section) => ({
id: section.id,
title: section.title,
rows: section.rows.map((row) => ({
id: row.id,
mediaId: row.mediaId,
heading: row.heading,
body: row.body,
percent: row.percent,
backgroundColor: row.backgroundColor,
textColor: row.textColor,
sheet: row.sheet,
catalogItems: row.catalogItems.map(({ kind, key }) => ({
kind,
key,
})),
highlights: row.highlights,
imageUrl: row.imageUrl,
imageWidth: row.imageWidth,
imageHeight: row.imageHeight,
})),
})),
}}
/>
</main>
</div>
);
}