186 lines
6.8 KiB
TypeScript
186 lines
6.8 KiB
TypeScript
import Link from "next/link";
|
|
import Image from "next/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 {
|
|
getActiveCatalog,
|
|
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("/admin/login");
|
|
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([
|
|
getActiveCatalog(),
|
|
getAdminGuideContent(guide.slug),
|
|
]);
|
|
if (!catalog || !content) notFound();
|
|
const guideCharacter = catalog.characters.find(
|
|
(item) => item.key === guide.characterKey,
|
|
);
|
|
return (
|
|
<div className="min-h-svh">
|
|
<AdminHeader />
|
|
<main className="mx-auto flex w-full max-w-6xl flex-col gap-6 p-4 sm:p-6 lg:p-8">
|
|
<header className="flex items-center gap-4">
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
render={<Link href={`/admin/${character}`} />}
|
|
nativeButton={false}
|
|
aria-label="กลับหน้า Guide"
|
|
>
|
|
<ArrowLeftIcon />
|
|
</Button>
|
|
<div className="relative size-16 shrink-0 overflow-hidden rounded-lg border">
|
|
<Image src={guide.coverUrl} alt={guide.name} fill 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>
|
|
</header>
|
|
<GuideSectionNav
|
|
guideId={guide.id}
|
|
characterKey={guide.characterKey}
|
|
current={page}
|
|
extras={extraSections.map(({ id, slug, title }) => ({ id, slug, title }))}
|
|
/>
|
|
<GuideSectionEditor
|
|
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,
|
|
rows: group.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,
|
|
note: group.note,
|
|
rows: group.rows,
|
|
})),
|
|
teams: content.teams.map((team) => ({
|
|
id: team.id,
|
|
title: team.title,
|
|
rotationSeconds: team.rotationSeconds,
|
|
combo: team.combo,
|
|
members: team.members.map((member) => ({
|
|
characterKey: member.characterKey,
|
|
weaponKey: member.weaponKey,
|
|
artifactKey: member.artifactKey,
|
|
secondArtifactKey: member.secondArtifactKey,
|
|
constellation: member.constellation,
|
|
refinement: member.refinement,
|
|
damage: member.damage,
|
|
})),
|
|
})),
|
|
teamGroups: content.teamGroups.map((group) => ({
|
|
id: group.id,
|
|
note: group.note,
|
|
teams: group.teams.map((team) => ({
|
|
id: team.id,
|
|
title: team.title,
|
|
rotationSeconds: team.rotationSeconds,
|
|
combo: team.combo,
|
|
members: team.members.map((member) => ({
|
|
characterKey: member.characterKey,
|
|
weaponKey: member.weaponKey,
|
|
artifactKey: member.artifactKey,
|
|
secondArtifactKey: member.secondArtifactKey,
|
|
constellation: member.constellation,
|
|
refinement: member.refinement,
|
|
damage: member.damage,
|
|
})),
|
|
})),
|
|
})),
|
|
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>
|
|
);
|
|
}
|