feat : update
CI / Verify (push) Successful in 1m9s
CI / Build immutable images and deploy (push) Successful in 5m9s

This commit is contained in:
2026-09-05 00:03:14 +07:00 Unverified
parent b4f9fb0abb
commit c8d6c44ba5
6 changed files with 57 additions and 10 deletions
+1 -1
View File
@@ -22,5 +22,5 @@ export default async function StructuredEditorPage({ params }: { params: Promise
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="min-w-0"><p className="text-sm text-muted-foreground">{guide.name}</p><h1 className="whitespace-nowrap font-heading text-3xl font-semibold">{guide.sections.find((section) => section.slug === page)?.title ?? page}</h1></div></header><GuideSectionNav characterKey={guide.characterKey} current={page as (typeof sections)[number]} /><GuideSectionEditor section={page as (typeof sections)[number]} 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 })) })), extras: content.extras.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, highlights: row.highlights, imageUrl: row.imageUrl, imageWidth: row.imageWidth, imageHeight: row.imageHeight })) })) }} /></main></div>;
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="min-w-0"><p className="text-sm text-muted-foreground">{guide.name}</p><h1 className="whitespace-nowrap font-heading text-3xl font-semibold">{guide.sections.find((section) => section.slug === page)?.title ?? page}</h1></div></header><GuideSectionNav characterKey={guide.characterKey} current={page as (typeof sections)[number]} guideId={guide.id} /><GuideSectionEditor section={page as (typeof sections)[number]} 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 })) })), extras: content.extras.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, highlights: row.highlights, imageUrl: row.imageUrl, imageWidth: row.imageWidth, imageHeight: row.imageHeight })) })) }} /></main></div>;
}
+1 -1
View File
@@ -27,5 +27,5 @@ export default async function GuideOverviewPage({ params }: { params: Promise<{
const { character } = await params;
const guide = await getGuideByCharacterKey(character);
if (!guide) notFound();
return <div className="min-h-svh"><AdminHeader /><main className="mx-auto flex w-full max-w-6xl flex-col gap-8 p-4 sm:p-6 lg:p-8"><header className="flex flex-wrap items-center gap-4"><Button variant="outline" size="icon" render={<Link href="/admin" />} nativeButton={false} aria-label="กลับ Dashboard"><ArrowLeftIcon /></Button><div className="relative size-16 overflow-hidden rounded-lg border"><Image src={guide.coverUrl} alt={guide.name} fill className="object-cover" /></div><div className="min-w-0 flex-1"><p className="text-sm text-muted-foreground">เลือกส่วนที่ต้องการแก้ไข</p><h1 className="font-heading text-3xl font-semibold">{guide.name}</h1></div><GuideVisibilityControl guideId={guide.id} guideName={guide.name} isPublic={guide.isPublic} /></header><GuideSectionNav characterKey={guide.characterKey} current="overview" /><GuideOverviewForm guide={guide} /><div className="grid gap-4 md:grid-cols-2">{sections.map(({ slug, title, description }) => <Link key={slug} href={`/admin/${guide.characterKey}/${slug}`} prefetch className="rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring"><Card className="h-full transition-colors hover:bg-muted/50"><CardHeader>{navigationIconPath(slug) ? <Image src={navigationIconPath(slug)!} alt="" width={40} height={40} className="size-10 object-contain" /> : null}<CardTitle>{title}</CardTitle><CardDescription>{description}</CardDescription><CardAction><ArrowUpRightIcon aria-hidden="true" className="size-5 text-primary" /></CardAction></CardHeader></Card></Link>)}<Link href={`/admin/${guide.characterKey}/extra`} prefetch className="rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring"><Card className="h-full transition-colors hover:bg-muted/50"><CardHeader><Image src={navigationIconPath("extra")!} alt="" width={40} height={40} className="size-10 object-contain" /><CardTitle>Extra sections</CardTitle><CardDescription>ส่วนเสริมแบบภาพ หัวข้อ ข้อความสั้น และเปอร์เซ็นต์</CardDescription><CardAction><ArrowUpRightIcon aria-hidden="true" className="size-5 text-primary" /></CardAction></CardHeader></Card></Link></div></main></div>;
return <div className="min-h-svh"><AdminHeader /><main className="mx-auto flex w-full max-w-6xl flex-col gap-8 p-4 sm:p-6 lg:p-8"><header className="flex flex-wrap items-center gap-4"><Button variant="outline" size="icon" render={<Link href="/admin" />} nativeButton={false} aria-label="กลับ Dashboard"><ArrowLeftIcon /></Button><div className="relative size-16 overflow-hidden rounded-lg border"><Image src={guide.coverUrl} alt={guide.name} fill className="object-cover" /></div><div className="min-w-0 flex-1"><p className="text-sm text-muted-foreground">เลือกส่วนที่ต้องการแก้ไข</p><h1 className="font-heading text-3xl font-semibold">{guide.name}</h1></div><GuideVisibilityControl guideId={guide.id} guideName={guide.name} isPublic={guide.isPublic} /></header><GuideSectionNav characterKey={guide.characterKey} current="overview" guideId={guide.id} /><GuideOverviewForm guide={guide} /><div className="grid gap-4 md:grid-cols-2">{sections.map(({ slug, title, description }) => <Link key={slug} href={`/admin/${guide.characterKey}/${slug}`} prefetch className="rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring"><Card className="h-full transition-colors hover:bg-muted/50"><CardHeader>{navigationIconPath(slug) ? <Image src={navigationIconPath(slug)!} alt="" width={40} height={40} className="size-10 object-contain" /> : null}<CardTitle>{title}</CardTitle><CardDescription>{description}</CardDescription><CardAction><ArrowUpRightIcon aria-hidden="true" className="size-5 text-primary" /></CardAction></CardHeader></Card></Link>)}<Link href={`/admin/${guide.characterKey}/extra`} prefetch className="rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring"><Card className="h-full transition-colors hover:bg-muted/50"><CardHeader><Image src={navigationIconPath("extra")!} alt="" width={40} height={40} className="size-10 object-contain" /><CardTitle>Extra sections</CardTitle><CardDescription>ส่วนเสริมแบบภาพ หัวข้อ ข้อความสั้น และเปอร์เซ็นต์</CardDescription><CardAction><ArrowUpRightIcon aria-hidden="true" className="size-5 text-primary" /></CardAction></CardHeader></Card></Link></div></main></div>;
}
+17
View File
@@ -140,4 +140,21 @@
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
::view-transition-old(*),
::view-transition-new(*),
::view-transition-group(*) {
animation-delay: 0s !important;
animation-duration: 0s !important;
}
}
::view-transition {
pointer-events: none;
}
::view-transition-group(.guide-topic-indicator) {
animation-duration: 300ms;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
z-index: -1;
}
+9 -4
View File
@@ -4,6 +4,7 @@ import Image from "next/image";
import Link from "next/link";
import type { MouseEvent } from "react";
import { GuideTopicIndicator } from "@/components/guide-topic-indicator";
import { ScrollableNav } from "@/components/public/scrollable-nav";
import { navigationIconPath } from "@/lib/navigation";
import { cn } from "@/lib/utils";
@@ -19,9 +20,11 @@ const sections = [
export function GuideSectionNav({
characterKey,
current,
guideId,
}: {
characterKey: string;
current: "overview" | (typeof sections)[number]["slug"];
guideId: string;
}) {
const confirmNavigation = (event: MouseEvent<HTMLAnchorElement>) => {
const browserWindow = window as Window & { __guideHasUnsavedChanges?: boolean };
@@ -39,10 +42,11 @@ export function GuideSectionNav({
prefetch
onClick={confirmNavigation}
className={cn(
"flex min-h-8 shrink-0 items-center gap-2 rounded-md px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring",
current === "overview" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground",
"relative isolate flex min-h-8 shrink-0 items-center gap-2 rounded-md px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring",
current === "overview" ? "text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground",
)}
>
{current === "overview" ? <GuideTopicIndicator guideId={guideId} surface="admin" /> : null}
<Image src={navigationIconPath("overview")!} alt="" width={20} height={20} className="size-5 object-contain" />
Overview
</Link>
@@ -55,10 +59,11 @@ export function GuideSectionNav({
prefetch
onClick={confirmNavigation}
className={cn(
"flex min-h-8 shrink-0 items-center gap-2 rounded-md px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring",
current === section.slug ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground",
"relative isolate flex min-h-8 shrink-0 items-center gap-2 rounded-md px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring",
current === section.slug ? "text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground",
)}
>
{current === section.slug ? <GuideTopicIndicator guideId={guideId} surface="admin" /> : null}
{iconPath ? <Image src={iconPath} alt="" width={20} height={20} className="size-5 object-contain" /> : null}
{section.label}
</Link>
+22
View File
@@ -0,0 +1,22 @@
import { ViewTransition } from "react";
export function GuideTopicIndicator({
guideId,
surface,
}: {
guideId: string;
surface: "public" | "admin";
}) {
return (
<ViewTransition
name={`guide-topic-indicator-${surface}-${guideId}`}
share="guide-topic-indicator"
default="none"
>
<span
aria-hidden="true"
className="absolute inset-0 -z-10 rounded-md bg-muted"
/>
</ViewTransition>
);
}
+7 -4
View File
@@ -21,6 +21,7 @@ import { GuideImageExport } from "@/components/public/guide-image-export";
import { ConstellationImage } from "@/components/public/constellation-image";
import { RichTextContent } from "@/components/rich-text-content";
import { LiveRefresh } from "@/components/live-refresh";
import { GuideTopicIndicator } from "@/components/guide-topic-indicator";
import { rarityGradientClass, rarityTextClass } from "@/lib/catalog/rarity";
import { elementIconFilter } from "@/lib/catalog/element";
import type { PublicGuide } from "@/lib/guides/queries";
@@ -126,13 +127,14 @@ export function StructuredGuideShell({
<Link
href={`/${guide.slug}`}
className={cn(
"flex min-h-8 shrink-0 items-center gap-2 whitespace-nowrap rounded-md px-3 text-sm transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring",
"relative isolate flex min-h-8 shrink-0 items-center gap-2 whitespace-nowrap rounded-md px-3 text-sm transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring",
!section
? "bg-muted text-foreground"
? "text-foreground"
: "text-muted-foreground hover:bg-muted hover:text-foreground",
)}
prefetch
>
{!section ? <GuideTopicIndicator guideId={guide.id} surface="public" /> : null}
<Image
src={navigationIconPath("overview")!}
alt=""
@@ -147,13 +149,14 @@ export function StructuredGuideShell({
key={item.id}
href={`/${guide.slug}/${item.slug}`}
className={cn(
"flex min-h-8 shrink-0 items-center gap-2 whitespace-nowrap rounded-md px-3 text-sm transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring",
"relative isolate flex min-h-8 shrink-0 items-center gap-2 whitespace-nowrap rounded-md px-3 text-sm transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring",
section === item.slug
? "bg-muted text-foreground"
? "text-foreground"
: "text-muted-foreground hover:bg-muted hover:text-foreground",
)}
prefetch
>
{section === item.slug ? <GuideTopicIndicator guideId={guide.id} surface="public" /> : null}
{navigationIconPath(item.slug) ? (
<Image
src={navigationIconPath(item.slug)!}